• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PHPCSStandards / PHP_CodeSniffer / 15637524486

13 Jun 2025 02:54PM UTC coverage: 78.436% (+0.06%) from 78.375%
15637524486

Pull #1108

github

web-flow
Merge ce5067991 into ef0b6a62c
Pull Request #1108: Squiz/SelfMemberReference: update XML doc

25193 of 32119 relevant lines covered (78.44%)

69.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.1
/src/Standards/PSR2/Sniffs/Classes/PropertyDeclarationSniff.php
1
<?php
2
/**
3
 * Verifies that properties are declared correctly.
4
 *
5
 * @author    Greg Sherwood <gsherwood@squiz.net>
6
 * @copyright 2006-2015 Squiz Pty Ltd (ABN 77 084 670 600)
7
 * @license   https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
8
 */
9

10
namespace PHP_CodeSniffer\Standards\PSR2\Sniffs\Classes;
11

12
use Exception;
13
use PHP_CodeSniffer\Files\File;
14
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
15
use PHP_CodeSniffer\Util\Tokens;
16

17
class PropertyDeclarationSniff extends AbstractVariableSniff
18
{
19

20

21
    /**
22
     * Processes the function tokens within the class.
23
     *
24
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
25
     * @param int                         $stackPtr  The position where the token was found.
26
     *
27
     * @return void
28
     */
29
    protected function processMemberVar(File $phpcsFile, $stackPtr)
3✔
30
    {
31
        $tokens = $phpcsFile->getTokens();
3✔
32

33
        if ($tokens[$stackPtr]['content'][1] === '_') {
3✔
34
            $error = 'Property name "%s" should not be prefixed with an underscore to indicate visibility';
3✔
35
            $data  = [$tokens[$stackPtr]['content']];
3✔
36
            $phpcsFile->addWarning($error, $stackPtr, 'Underscore', $data);
3✔
37
        }
1✔
38

39
        // Detect multiple properties defined at the same time. Throw an error
40
        // for this, but also only process the first property in the list so we don't
41
        // repeat errors.
42
        $find   = Tokens::$scopeModifiers;
3✔
43
        $find[] = T_VARIABLE;
3✔
44
        $find[] = T_VAR;
3✔
45
        $find[] = T_READONLY;
3✔
46
        $find[] = T_FINAL;
3✔
47
        $find[] = T_SEMICOLON;
3✔
48
        $find[] = T_OPEN_CURLY_BRACKET;
3✔
49

50
        $prev = $phpcsFile->findPrevious($find, ($stackPtr - 1));
3✔
51
        if ($tokens[$prev]['code'] === T_VARIABLE) {
3✔
52
            return;
3✔
53
        }
54

55
        if ($tokens[$prev]['code'] === T_VAR) {
3✔
56
            $error = 'The var keyword must not be used to declare a property';
3✔
57
            $phpcsFile->addError($error, $stackPtr, 'VarUsed');
3✔
58
        }
1✔
59

60
        $next = $phpcsFile->findNext([T_VARIABLE, T_SEMICOLON], ($stackPtr + 1));
3✔
61
        if ($next !== false && $tokens[$next]['code'] === T_VARIABLE) {
3✔
62
            $error = 'There must not be more than one property declared per statement';
3✔
63
            $phpcsFile->addError($error, $stackPtr, 'Multiple');
3✔
64
        }
1✔
65

66
        try {
67
            $propertyInfo = $phpcsFile->getMemberProperties($stackPtr);
3✔
68
            if (empty($propertyInfo) === true) {
3✔
69
                return;
2✔
70
            }
71
        } catch (Exception $e) {
1✔
72
            // Turns out not to be a property after all.
73
            return;
×
74
        }
75

76
        if ($propertyInfo['type'] !== '') {
3✔
77
            $typeToken = $propertyInfo['type_end_token'];
3✔
78
            $error     = 'There must be 1 space after the property type declaration; %s found';
3✔
79
            if ($tokens[($typeToken + 1)]['code'] !== T_WHITESPACE) {
3✔
80
                $data = ['0'];
3✔
81
                $fix  = $phpcsFile->addFixableError($error, $typeToken, 'SpacingAfterType', $data);
3✔
82
                if ($fix === true) {
3✔
83
                    $phpcsFile->fixer->addContent($typeToken, ' ');
3✔
84
                }
1✔
85
            } else if ($tokens[($typeToken + 1)]['content'] !== ' ') {
3✔
86
                $next = $phpcsFile->findNext(T_WHITESPACE, ($typeToken + 1), null, true);
3✔
87
                if ($tokens[$next]['line'] !== $tokens[$typeToken]['line']) {
3✔
88
                    $found = 'newline';
3✔
89
                } else {
1✔
90
                    $found = $tokens[($typeToken + 1)]['length'];
3✔
91
                }
92

93
                $data = [$found];
3✔
94

95
                $nextNonWs = $phpcsFile->findNext(Tokens::$emptyTokens, ($typeToken + 1), null, true);
3✔
96
                if ($nextNonWs !== $next) {
3✔
97
                    $phpcsFile->addError($error, $typeToken, 'SpacingAfterType', $data);
×
98
                } else {
99
                    $fix = $phpcsFile->addFixableError($error, $typeToken, 'SpacingAfterType', $data);
3✔
100
                    if ($fix === true) {
3✔
101
                        if ($found === 'newline') {
3✔
102
                            $phpcsFile->fixer->beginChangeset();
3✔
103
                            for ($x = ($typeToken + 1); $x < $next; $x++) {
3✔
104
                                $phpcsFile->fixer->replaceToken($x, '');
3✔
105
                            }
1✔
106

107
                            $phpcsFile->fixer->addContent($typeToken, ' ');
3✔
108
                            $phpcsFile->fixer->endChangeset();
3✔
109
                        } else {
1✔
110
                            $phpcsFile->fixer->replaceToken(($typeToken + 1), ' ');
3✔
111
                        }
112
                    }
1✔
113
                }
114
            }//end if
1✔
115
        }//end if
1✔
116

117
        if ($propertyInfo['scope_specified'] === false && $propertyInfo['set_scope'] === false) {
3✔
118
            $error = 'Visibility must be declared on property "%s"';
3✔
119
            $data  = [$tokens[$stackPtr]['content']];
3✔
120
            $phpcsFile->addError($error, $stackPtr, 'ScopeMissing', $data);
3✔
121
        }
1✔
122

123
        /*
124
         * Note: per PSR-PER section 4.6 v 2.1/3.0, the order should be:
125
         * - Inheritance modifier: `abstract` or `final`.
126
         * - Visibility modifier: `public`, `protected`, or `private`.
127
         * - Set-visibility modifier: `public(set)`, `protected(set)`, or `private(set)`
128
         * - Scope modifier: `static`.
129
         * - Mutation modifier: `readonly`.
130
         * - Type declaration.
131
         * - Name.
132
         *
133
         * Ref:
134
         * - https://www.php-fig.org/per/coding-style/#46-modifier-keywords
135
         * - https://github.com/php-fig/per-coding-style/pull/99
136
         *
137
         * The `static` and `readonly` modifiers are mutually exclusive and cannot be used together.
138
         *
139
         * Based on that, the below modifier keyword order checks are sufficient (for now).
140
         */
141

142
        $hasVisibilityModifier   = ($propertyInfo['scope_specified'] === true || $propertyInfo['set_scope'] !== false);
3✔
143
        $lastVisibilityModifier  = $phpcsFile->findPrevious(Tokens::$scopeModifiers, ($stackPtr - 1));
3✔
144
        $firstVisibilityModifier = $lastVisibilityModifier;
3✔
145

146
        if ($propertyInfo['scope_specified'] === true && $propertyInfo['set_scope'] !== false) {
3✔
147
            $scopePtr    = $phpcsFile->findPrevious([T_PUBLIC, T_PROTECTED, T_PRIVATE], ($stackPtr - 1));
3✔
148
            $setScopePtr = $phpcsFile->findPrevious([T_PUBLIC_SET, T_PROTECTED_SET, T_PRIVATE_SET], ($stackPtr - 1));
3✔
149
            if ($scopePtr > $setScopePtr) {
3✔
150
                $error = 'The "read"-visibility must come before the "write"-visibility';
3✔
151
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'AvizKeywordOrder');
3✔
152
                if ($fix === true) {
3✔
153
                    $phpcsFile->fixer->beginChangeset();
3✔
154

155
                    for ($i = ($scopePtr + 1); $scopePtr < $stackPtr; $i++) {
3✔
156
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
157
                            break;
3✔
158
                        }
159

160
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
161
                    }
1✔
162

163
                    $phpcsFile->fixer->replaceToken($scopePtr, '');
3✔
164
                    $phpcsFile->fixer->addContentBefore($setScopePtr, $tokens[$scopePtr]['content'].' ');
3✔
165

166
                    $phpcsFile->fixer->endChangeset();
3✔
167
                }
1✔
168
            }
1✔
169

170
            $firstVisibilityModifier = min($scopePtr, $setScopePtr);
3✔
171
        }//end if
1✔
172

173
        if ($hasVisibilityModifier === true && $propertyInfo['is_final'] === true) {
3✔
174
            $scopePtr = $firstVisibilityModifier;
3✔
175
            $finalPtr = $phpcsFile->findPrevious(T_FINAL, ($stackPtr - 1));
3✔
176
            if ($finalPtr > $scopePtr) {
3✔
177
                $error = 'The final declaration must come before the visibility declaration';
3✔
178
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'FinalAfterVisibility');
3✔
179
                if ($fix === true) {
3✔
180
                    $phpcsFile->fixer->beginChangeset();
3✔
181

182
                    for ($i = ($finalPtr + 1); $finalPtr < $stackPtr; $i++) {
3✔
183
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
184
                            break;
3✔
185
                        }
186

187
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
188
                    }
1✔
189

190
                    $phpcsFile->fixer->replaceToken($finalPtr, '');
3✔
191
                    $phpcsFile->fixer->addContentBefore($scopePtr, $tokens[$finalPtr]['content'].' ');
3✔
192

193
                    $phpcsFile->fixer->endChangeset();
3✔
194
                }
1✔
195
            }
1✔
196
        }//end if
1✔
197

198
        if ($hasVisibilityModifier === true && $propertyInfo['is_static'] === true) {
3✔
199
            $scopePtr  = $lastVisibilityModifier;
3✔
200
            $staticPtr = $phpcsFile->findPrevious(T_STATIC, ($stackPtr - 1));
3✔
201
            if ($scopePtr > $staticPtr) {
3✔
202
                $error = 'The static declaration must come after the visibility declaration';
3✔
203
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'StaticBeforeVisibility');
3✔
204
                if ($fix === true) {
3✔
205
                    $phpcsFile->fixer->beginChangeset();
3✔
206

207
                    for ($i = ($staticPtr + 1); $staticPtr < $stackPtr; $i++) {
3✔
208
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
209
                            break;
3✔
210
                        }
211

212
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
213
                    }
1✔
214

215
                    $phpcsFile->fixer->replaceToken($staticPtr, '');
3✔
216
                    $phpcsFile->fixer->addContent($scopePtr, ' '.$tokens[$staticPtr]['content']);
3✔
217

218
                    $phpcsFile->fixer->endChangeset();
3✔
219
                }
1✔
220
            }
1✔
221
        }//end if
1✔
222

223
        if ($hasVisibilityModifier === true && $propertyInfo['is_readonly'] === true) {
3✔
224
            $scopePtr    = $lastVisibilityModifier;
3✔
225
            $readonlyPtr = $phpcsFile->findPrevious(T_READONLY, ($stackPtr - 1));
3✔
226
            if ($scopePtr > $readonlyPtr) {
3✔
227
                $error = 'The readonly declaration must come after the visibility declaration';
3✔
228
                $fix   = $phpcsFile->addFixableError($error, $stackPtr, 'ReadonlyBeforeVisibility');
3✔
229
                if ($fix === true) {
3✔
230
                    $phpcsFile->fixer->beginChangeset();
3✔
231

232
                    for ($i = ($readonlyPtr + 1); $readonlyPtr < $stackPtr; $i++) {
3✔
233
                        if ($tokens[$i]['code'] !== T_WHITESPACE) {
3✔
234
                            break;
3✔
235
                        }
236

237
                        $phpcsFile->fixer->replaceToken($i, '');
3✔
238
                    }
1✔
239

240
                    $phpcsFile->fixer->replaceToken($readonlyPtr, '');
3✔
241
                    $phpcsFile->fixer->addContent($scopePtr, ' '.$tokens[$readonlyPtr]['content']);
3✔
242

243
                    $phpcsFile->fixer->endChangeset();
3✔
244
                }
1✔
245
            }
1✔
246
        }//end if
1✔
247

248
    }//end processMemberVar()
2✔
249

250

251
    /**
252
     * Processes normal variables.
253
     *
254
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
255
     * @param int                         $stackPtr  The position where the token was found.
256
     *
257
     * @return void
258
     */
259
    protected function processVariable(File $phpcsFile, $stackPtr)
×
260
    {
261
        /*
262
            We don't care about normal variables.
263
        */
264

265
    }//end processVariable()
×
266

267

268
    /**
269
     * Processes variables in double quoted strings.
270
     *
271
     * @param \PHP_CodeSniffer\Files\File $phpcsFile The file where this token was found.
272
     * @param int                         $stackPtr  The position where the token was found.
273
     *
274
     * @return void
275
     */
276
    protected function processVariableInString(File $phpcsFile, $stackPtr)
×
277
    {
278
        /*
279
            We don't care about normal variables.
280
        */
281

282
    }//end processVariableInString()
×
283

284

285
}//end class
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc