Skip to content

Commit be92b75

Browse files
committed
Add stricter naming convention sniffs for CakePHP 6.x
- Update ValidFunctionNameSniff to flag underscore-prefixed protected/private methods (except Entity accessor/mutator pattern _get*, _set*) - Add ValidPropertyNameSniff to flag underscore-prefixed properties - Remove PSR2.Classes.PropertyDeclaration.Underscore exclusion from ruleset - Add new Slevomat sniffs: - RequireNullCoalesceEqualOperator - RequireNullSafeObjectOperator - RequireSelfReference - NullTypeHintOnLastPosition - Fix $_magicMethods property naming in sniff itself - Add T_ENUM support to ValidFunctionNameSniff
1 parent 8c94811 commit be92b75

7 files changed

Lines changed: 186 additions & 13 deletions

File tree

CakePHP/Sniffs/NamingConventions/ValidFunctionNameSniff.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ class ValidFunctionNameSniff extends AbstractScopeSniff
2727
/**
2828
* A list of all PHP magic methods.
2929
*
30-
* @var array
30+
* @var array<string>
3131
*/
32-
protected array $_magicMethods = [
32+
protected array $magicMethods = [
3333
'construct',
3434
'destruct',
3535
'call',
@@ -54,7 +54,7 @@ class ValidFunctionNameSniff extends AbstractScopeSniff
5454
*/
5555
public function __construct()
5656
{
57-
parent::__construct([T_CLASS, T_INTERFACE, T_TRAIT], [T_FUNCTION], true);
57+
parent::__construct([T_CLASS, T_INTERFACE, T_TRAIT, T_ENUM], [T_FUNCTION], true);
5858
}
5959

6060
/**
@@ -71,7 +71,7 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop
7171
$errorData = [$className . '::' . $methodName];
7272

7373
// Ignore magic methods
74-
if (preg_match('/^__(' . implode('|', $this->_magicMethods) . ')$/', $methodName)) {
74+
if (preg_match('/^__(' . implode('|', $this->magicMethods) . ')$/', $methodName)) {
7575
return;
7676
}
7777

@@ -89,6 +89,17 @@ protected function processTokenWithinScope(File $phpcsFile, $stackPtr, $currScop
8989

9090
return;
9191
}
92+
93+
// Check non-public methods for underscore prefix
94+
if ($isPublic === false && $methodName[0] === '_') {
95+
// Allow CakePHP Entity accessor/mutator pattern: _getField(), _setField()
96+
if (preg_match('/^_(get|set)[A-Z]/', $methodName)) {
97+
return;
98+
}
99+
100+
$error = 'Non-public method name "%s" should not be prefixed with underscore';
101+
$phpcsFile->addError($error, $stackPtr, 'ProtectedWithUnderscore', $errorData);
102+
}
92103
}
93104

94105
/**
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
/**
3+
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4+
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5+
*
6+
* Licensed under The MIT License
7+
* For full copyright and license information, please see the LICENSE.txt
8+
* Redistributions of files must retain the above copyright notice.
9+
*
10+
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11+
* @link https://github.com/cakephp/cakephp-codesniffer
12+
* @since CakePHP CodeSniffer 6.0.0
13+
* @license https://www.opensource.org/licenses/mit-license.php MIT License
14+
*/
15+
16+
/**
17+
* Ensures property names follow PSR naming conventions.
18+
*
19+
* Non-public properties should not be prefixed with an underscore.
20+
*/
21+
namespace CakePHP\Sniffs\NamingConventions;
22+
23+
use PHP_CodeSniffer\Files\File;
24+
use PHP_CodeSniffer\Sniffs\AbstractVariableSniff;
25+
26+
class ValidPropertyNameSniff extends AbstractVariableSniff
27+
{
28+
/**
29+
* @inheritDoc
30+
*/
31+
protected function processMemberVar(File $phpcsFile, $stackPtr)
32+
{
33+
$tokens = $phpcsFile->getTokens();
34+
$propName = ltrim($tokens[$stackPtr]['content'], '$');
35+
36+
// Only check properties starting with underscore
37+
if ($propName[0] !== '_') {
38+
return;
39+
}
40+
41+
$props = $phpcsFile->getMemberProperties($stackPtr);
42+
43+
// Public properties with underscore are also bad, but less common
44+
// Focus on protected/private which was the old convention
45+
if ($props['scope'] !== 'public') {
46+
$error = 'Non-public property "$%s" should not be prefixed with underscore';
47+
$phpcsFile->addError($error, $stackPtr, 'PropertyWithUnderscore', [$propName]);
48+
} else {
49+
$error = 'Public property "$%s" must not be prefixed with underscore';
50+
$phpcsFile->addError($error, $stackPtr, 'PublicPropertyWithUnderscore', [$propName]);
51+
}
52+
}
53+
54+
/**
55+
* @inheritDoc
56+
*/
57+
protected function processVariable(File $phpcsFile, $stackPtr)
58+
{
59+
// We only care about member variables (properties)
60+
}
61+
62+
/**
63+
* @inheritDoc
64+
*/
65+
protected function processVariableInString(File $phpcsFile, $stackPtr)
66+
{
67+
// We only care about member variables (properties)
68+
}
69+
}

CakePHP/Tests/NamingConventions/ValidFunctionNameUnitTest.inc

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ class FunctionNames
2929

3030
protected function _someFunc()
3131
{
32-
// code here
32+
// code here - error: underscore prefix not allowed
3333
}
3434

3535
protected function noUnderscorePrefix()
@@ -40,6 +40,22 @@ class FunctionNames
4040
};
4141
}
4242

43+
// Entity accessor/mutator patterns - should be allowed
44+
protected function _getName()
45+
{
46+
return $this->name;
47+
}
48+
49+
protected function _setName($name)
50+
{
51+
$this->name = $name;
52+
}
53+
54+
protected function _getFullName()
55+
{
56+
return $this->first_name . ' ' . $this->last_name;
57+
}
58+
4359
public function __call($name, $arguments)
4460
{
4561
}

CakePHP/Tests/NamingConventions/ValidFunctionNameUnitTest.php

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,21 @@ class ValidFunctionNameUnitTest extends AbstractSniffTestCase
99
/**
1010
* @inheritDoc
1111
*/
12-
public function getErrorList()
12+
public function getErrorList(): array
1313
{
1414
return [
15-
6 => 1,
16-
87 => 1,
17-
96 => 1,
15+
6 => 1, // public function _forbidden
16+
30 => 1, // protected function _someFunc
17+
103 => 1, // public function _forbidden (interface)
18+
112 => 1, // public function _forbidden (trait)
19+
136 => 1, // protected function _someFunc (trait)
1820
];
1921
}
2022

2123
/**
2224
* @inheritDoc
2325
*/
24-
public function getWarningList()
26+
public function getWarningList(): array
2527
{
2628
return [];
2729
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace Beakman;
4+
5+
class PropertyNames
6+
{
7+
public string $publicProperty = '';
8+
9+
public string $_publicUnderscore = ''; // error
10+
11+
protected string $protectedProperty = '';
12+
13+
protected string $_protectedUnderscore = ''; // error
14+
15+
private string $privateProperty = '';
16+
17+
private string $_privateUnderscore = ''; // error
18+
19+
public static string $publicStatic = '';
20+
21+
protected static string $_protectedStatic = ''; // error
22+
23+
private static string $_privateStatic = ''; // error
24+
}
25+
26+
trait PropertyNamesTrait
27+
{
28+
public string $publicProperty = '';
29+
30+
public string $_publicUnderscore = ''; // error
31+
32+
protected string $protectedProperty = '';
33+
34+
protected string $_protectedUnderscore = ''; // error
35+
36+
private string $privateProperty = '';
37+
38+
private string $_privateUnderscore = ''; // error
39+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
namespace CakePHP\Tests\NamingConventions;
4+
5+
use PHP_CodeSniffer\Tests\Standards\AbstractSniffTestCase;
6+
7+
class ValidPropertyNameUnitTest extends AbstractSniffTestCase
8+
{
9+
/**
10+
* @inheritDoc
11+
*/
12+
public function getErrorList(): array
13+
{
14+
return [
15+
9 => 1, // public $_publicUnderscore
16+
13 => 1, // protected $_protectedUnderscore
17+
17 => 1, // private $_privateUnderscore
18+
21 => 1, // protected static $_protectedStatic
19+
23 => 1, // private static $_privateStatic
20+
30 => 1, // public $_publicUnderscore (trait)
21+
34 => 1, // protected $_protectedUnderscore (trait)
22+
38 => 1, // private $_privateUnderscore (trait)
23+
];
24+
}
25+
26+
/**
27+
* @inheritDoc
28+
*/
29+
public function getWarningList(): array
30+
{
31+
return [];
32+
}
33+
}

CakePHP/ruleset.xml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@
1818
<exclude name="PSR12.Files.FileHeader.SpacingAfterUseBlock"/>
1919
<exclude name="PSR12.Files.FileHeader.SpacingAfterUseFunctionBlock"/>
2020
<!--
21-
Property and method names with underscore prefix are allowed in CakePHP.
22-
Not using underscore prefix is a recommendation of PSR2, not a requirement.
21+
We use our own CakePHP.NamingConventions.ValidFunctionName sniff which allows
22+
Entity accessor/mutator pattern (_getField, _setField) while enforcing PSR naming elsewhere.
2323
-->
24-
<exclude name="PSR2.Classes.PropertyDeclaration.Underscore"/>
2524
<exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
2625
</rule>
2726

@@ -256,6 +255,10 @@
256255
<rule ref="SlevomatCodingStandard.Attributes.RequireAttributeAfterDocComment"/>
257256
<rule ref="SlevomatCodingStandard.Classes.BackedEnumTypeSpacing"/>
258257
<rule ref="SlevomatCodingStandard.ControlStructures.RequireShortTernaryOperator"/>
258+
<rule ref="SlevomatCodingStandard.ControlStructures.RequireNullCoalesceEqualOperator"/>
259+
<rule ref="SlevomatCodingStandard.ControlStructures.RequireNullSafeObjectOperator"/>
260+
<rule ref="SlevomatCodingStandard.Classes.RequireSelfReference"/>
261+
<rule ref="SlevomatCodingStandard.TypeHints.NullTypeHintOnLastPosition"/>
259262

260263
<!-- phpcs Zend sniffs -->
261264
<rule ref="Zend.NamingConventions.ValidVariableName">

0 commit comments

Comments
 (0)