Skip to content

Commit 10c3f28

Browse files
committed
Add new "About Standards" page
This new page contains more generic information about: * The difference between a project ruleset and a standard. * Requirements for a standard. * Naming conventions for sniffs.
1 parent d41944d commit 10c3f28

File tree

4 files changed

+280
-3
lines changed

4 files changed

+280
-3
lines changed
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
## Table of contents
2+
3+
* [A Project ruleset or a standard ?](#a-project-ruleset-or-a-standard-)
4+
* [About standards](#about-standards)
5+
* [Creating an external standard for PHP_CodeSniffer](#creating-an-external-standard-for-php_codesniffer)
6+
* [Creating new rules](#creating-new-rules)
7+
* [Naming conventions](#naming-conventions)
8+
9+
***
10+
11+
## A Project ruleset or a standard ?
12+
13+
The terminology used for coding standards in the context of PHP_CodeSniffer can be confusing.
14+
When do you use a project ruleset ? When should you use a standard ?
15+
16+
Let's try and clarify this.
17+
18+
As a general rule of thumb: individual projects should use a project ruleset to document the rules enforced for the project.
19+
Project rulesets may _include_ one or more predefined standards.
20+
21+
If a group of projects should use the same rules, you could create an external standard to define the common rules. This external standard can then be included in the project specific ruleset for each individual project.
22+
23+
The typical differences between project-specific rulesets and standards are:
24+
25+
| | Project specific ruleset | External standard |
26+
|-------------------------------------------------------------------------------|--------------------------|--------------------------------|
27+
| File name | `[.]phpcs.xml[.dist]` | `ruleset.xml` |
28+
| Location | Project root directory | Standard directory (see below) |
29+
| Will automatically be used when no standard is provided on the command line ? | Yes | No |
30+
| Can have custom sniffs ? | No | Yes |
31+
| Can be installed ? | No | Yes |
32+
| Reusability by other projects ? | Limited | Yes |
33+
34+
For optimal reusability, it is in most cases a good idea for a standard to be in its own repository and to be maintained as a separate project.
35+
36+
A `[.]phpcs.xml[.dist]` file and a `ruleset.xml` file can largely contain the same type of directives.
37+
The [Annotated ruleset](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Annotated-Ruleset) page contains information on all the directives you can use.
38+
39+
Keep in mind that for a _standard_ to be optimally reusable, it should not contain project specific information, such as `<file>` directives or `<exclude-patterns>`, while a project specific `[.]phpcs.xml[.dist]` ruleset file _can_ contain that information.
40+
41+
You may also find the [Customisable Sniff Properties](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Customisable-Sniff-Properties) page a handy reference for customisations which can be made to PHP_CodeSniffer native sniffs.
42+
43+
<p align="right"><a href="#table-of-contents">back to top</a></p>
44+
45+
46+
## About standards
47+
48+
In the context of PHP_CodeSniffer, a _"standard"_ is a predefined collection of rules for code to follow.
49+
Examples of standards are: PSR2 and PSR12, but a standard can also be eco-system specific, such as a Joomla or Doctrine standard, company specific or specific to a group of (related) projects.
50+
51+
PHP_CodeSniffer has a number of build-in standards. However, if you want to define your own standard, you can.
52+
In terms of PHP_CodeSniffer, this is regarded as an _"external standard"_ and the path to the standard can be registered with PHP_CodeSniffer using the `--config-set installed_paths path/to/standard` command.
53+
54+
A standard _may_ contain sniffs, but it doesn't have to. More about this below.
55+
56+
<p align="right"><a href="#table-of-contents">back to top</a></p>
57+
58+
59+
## Creating an external standard for PHP_CodeSniffer
60+
61+
The first thing to do is to think of a name for the standard.
62+
63+
Rules to keep in mind:
64+
* The standard name CANNOT contain spaces, `.` characters or slashes.
65+
* The standard name CANNOT be named "Internal".
66+
* IF the standard will contain custom sniffs, the standard name MUST be a name which can be used in a PHP namespace, i.e. it is not allowed to start with a number, not allowed to contain dashes etc.
67+
68+
Once you have a good name:
69+
* Create a directory with that name (case-sensitive).
70+
* Create a file in that directory called `ruleset.xml` with the following contents and replace `StandardName` in the below code snippet with your chosen name.
71+
```xml
72+
<?xml version="1.0"?>
73+
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="StandardName" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/PHPCSStandards/PHP_CodeSniffer/master/phpcs.xsd">
74+
75+
</ruleset>
76+
```
77+
78+
If your chosen standard name is `MyCompanyStandard`, the directory structure would now look like this:
79+
```
80+
- MyCompanyStandard (directory)
81+
- ruleset.xml (file)
82+
```
83+
84+
Now you can start adding rules to the standard.
85+
86+
For example, if your standard wants to enforce space indentation, you can add the `Generic.WhiteSpace.DisallowTabIndent` sniff to your ruleset:
87+
```xml
88+
<?xml version="1.0"?>
89+
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="YourStandardName" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/PHPCSStandards/PHP_CodeSniffer/master/phpcs.xsd">
90+
91+
<rule ref="Generic.WhiteSpace.DisallowTabIndent"/>
92+
93+
</ruleset>
94+
```
95+
96+
As mentioned before, have a look at the [Annotated ruleset](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Annotated-Ruleset) and the [Customisable Sniff Properties](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Customisable-Sniff-Properties) pages for inspiration on what directives and customisations you can add to your ruleset.
97+
98+
<p align="right"><a href="#table-of-contents">back to top</a></p>
99+
100+
101+
## Creating new rules
102+
103+
There may be rules you want to enforce for which you cannot find an existing sniff, either in the PHP_CodeSniffer build-in standards or in any of the available generic external standards.
104+
105+
In that case, you can write your own sniff to enforce that rule.
106+
107+
> If the sniff could be generically usable, please be a good open source citizen and consider opening an issue in PHP_CodeSniffer, or in one of the external standards repositories, and offer to contribute the sniff, making it available for others to use as well.
108+
109+
> [!IMPORTANT]
110+
> All sniffs in a standard are automatically included. There is no need to include the sniff(s) in the `ruleset.xml` via a `<rule ref=.../>`.
111+
112+
113+
There is a [Coding Standard Tutorial](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/Coding-Standard-Tutorial) available on how to write a sniff.
114+
115+
<p align="right"><a href="#table-of-contents">back to top</a></p>
116+
117+
118+
### Naming conventions
119+
120+
Sniffs need to follow strict directory layout and naming conventions to allow for PHP_CodeSniffer to autoload sniff files correctly, translate between sniff codes and sniff class names, and to set any customisations indicated in a ruleset for a particular sniff.
121+
122+
#### 1. Directory structure
123+
124+
A sniff MUST belong to a standard and MUST be in a category.
125+
126+
The directory structure MUST be as follows:
127+
```
128+
[StandardName]/Sniffs/[CategoryName]/
129+
```
130+
131+
* The `[StandardName]` directory MAY be nested deeper inside a project.
132+
* No directories should exist under the `[CategoryName]` directory.
133+
134+
#### 2. Sniff file name
135+
136+
All sniff files MUST end on `Sniff.php` and be located within a `[CategoryName]` directory.
137+
138+
All sniffs MUST have a name, so a sniff class called just and only `Sniff` is not allowed.
139+
140+
Both the sniff name and the category name MUST be valid symbol names in PHP.
141+
142+
143+
#### 3. Namespace and class name
144+
145+
The namespace and class name MUST follow [PSR-4](https://www.php-fig.org/psr/psr-4/).
146+
147+
This means that - taking the example directory structure above in to account - the namespace name MUST end on `[StandardName]\Sniffs\[CategoryName]` and the class name MUST be exactly the same as the file name (minus the `.php` file extension).
148+
149+
> [!NOTE]
150+
> As long as an external standard is registered with PHP_CodeSniffer via `installed_paths` and the standard follows the directory layout and naming conventions, PHP_CodeSniffer can, and will, automatically handle the sniff autoloading.
151+
>
152+
> As the PHP_CodeSniffer autoloader is essential for the translation between sniff codes (as used in rulesets) and sniff class names, it is strongly discouraged to set up autoloading of sniff classes via Composer, as this can interfere with the _error code to sniff class_ translation.
153+
154+
155+
#### Examples
156+
157+
158+
##### Valid:
159+
```
160+
<?php
161+
// File: MyStandard/Sniffs/Operators/OperatorSpacingSniff.php
162+
163+
namespace MyStandard\Sniffs\Operators;
164+
165+
use PHP_CodeSniffer\Files\File;
166+
use PHP_CodeSniffer\Sniffs\Sniff;
167+
168+
class OperatorSpacingSniff implements Sniff {...}
169+
```
170+
171+
Prefixing the namespace is allowed:
172+
```
173+
<?php
174+
// File: MyStandard/Sniffs/Operators/OperatorSpacingSniff.php
175+
176+
namespace NS\Prefix\MyStandard\Sniffs\Operators;
177+
178+
use PHP_CodeSniffer\Files\File;
179+
use PHP_CodeSniffer\Sniffs\Sniff;
180+
181+
class OperatorSpacingSniff implements Sniff {...}
182+
```
183+
184+
Be sure to inform PHP_CodeSniffer about the namespace prefix by annotating it in the `ruleset.xml` file in the `namespace` attribute on the `ruleset` node:
185+
```xml
186+
<?xml version="1.0"?>
187+
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="MyStandard" namespace="NS\Prefix\MyStandard" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/PHPCSStandards/PHP_CodeSniffer/master/phpcs.xsd">
188+
189+
</ruleset>
190+
```
191+
192+
Nesting the standard directory deeper inside a project is allowed:
193+
```
194+
<?php
195+
// File: src/Tools/PHPCS/MyStandard/Sniffs/Operators/OperatorSpacingSniff.php
196+
197+
namespace Vendor\Tools\PHPCS\MyStandard\Sniffs\Operators;
198+
199+
use PHP_CodeSniffer\Files\File;
200+
use PHP_CodeSniffer\Sniffs\Sniff;
201+
202+
class OperatorSpacingSniff implements Sniff {...}
203+
```
204+
The same note about setting the `namespace` attribute in the `ruleset.xml` file applies.
205+
206+
Also make sure that the `installed_paths` configuration option is set correctly and points to the `MyStandard` directory.
207+
208+
209+
##### Invalid:
210+
211+
Sniff name not ending on `Sniff`:
212+
```
213+
<?php
214+
// File: MyStandard/Sniffs/Operators/OperatorSpacing.php
215+
216+
namespace MyStandard\Sniffs\Operators;
217+
218+
use PHP_CodeSniffer\Files\File;
219+
use PHP_CodeSniffer\Sniffs\Sniff;
220+
221+
class OperatorSpacing implements Sniff {...}
222+
```
223+
224+
Sniff not placed in a (sub-)directory under a `Sniffs` directory:
225+
```
226+
<?php
227+
// File: MyStandard/Operators/OperatorSpacing.php
228+
229+
namespace MyStandard\Operators;
230+
231+
use PHP_CodeSniffer\Files\File;
232+
use PHP_CodeSniffer\Sniffs\Sniff;
233+
234+
class OperatorSpacingSniff implements Sniff {...}
235+
```
236+
237+
Not following the required directory structure (missing `[CategoryName]` subdirectory):
238+
```
239+
<?php
240+
// File: MyStandard/Sniffs/OperatorSpacingSniff.php
241+
242+
namespace MyStandard\Sniffs;
243+
244+
use PHP_CodeSniffer\Files\File;
245+
use PHP_CodeSniffer\Sniffs\Sniff;
246+
247+
class OperatorSpacingSniff implements Sniff {...}
248+
```
249+
250+
Not following the required directory structure (missing `[StandardName]` top-level directory):
251+
```
252+
<?php
253+
// File: src/Sniffs/Operators/OperatorSpacingSniff.php
254+
255+
namespace Sniffs\Operators;
256+
257+
use PHP_CodeSniffer\Files\File;
258+
use PHP_CodeSniffer\Sniffs\Sniff;
259+
260+
class OperatorSpacingSniff implements Sniff {...}
261+
```
262+
263+
<p align="right"><a href="#table-of-contents">back to top</a></p>

wiki/Annotated-Ruleset.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,23 @@ PHP_CodeSniffer allows developers to design their own coding standards by creati
22

33
Once created, a ruleset file can be used with the `--standard` command line argument. In the following example, PHP_CodeSniffer will use the coding standard defined in a custom ruleset file called custom_ruleset.xml:
44

5-
$ phpcs --standard=/path/to/custom_ruleset.xml test.php
5+
```bash
6+
$ phpcs --standard=/path/to/custom_ruleset.xml test.php
7+
```
8+
9+
By default, PHP_CodeSniffer will look for the following files: `.phpcs.xml`, `phpcs.xml`, `.phpcs.xml.dist`, `phpcs.xml.dist`.
10+
If you use one of these names for your standard, PHP_CodeSniffer will automatically find anf use it if no `--standard` is provided on the command-line.
11+
12+
For projects, it is recommended to use one of the `.dist` filenames.
13+
614

715
## The Annotated Sample File
816

917
The following sample file documents the ruleset.xml format and shows you the complete range of features that the format supports. The file is designed for documentation purposes only and is not a working coding standard.
1018

1119
```xml
1220
<?xml version="1.0"?>
13-
<ruleset name="Custom Standard" namespace="MyProject\CS\Standard">
21+
<ruleset name="CustomStandard" namespace="MyProject\CS\CustomStandard">
1422

1523
<!--
1624
The name attribute of the ruleset tag is displayed
@@ -190,6 +198,9 @@ The following sample file documents the ruleset.xml format and shows you the com
190198
If you are including sniffs that are not installed, you can
191199
reference the sniff class using an absolute or relative path
192200
instead of using the sniff code.
201+
Sniffs included via a path reference should still follow the
202+
naming conventions for PHP_CodeSniffer as outlined in
203+
https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/About-Standards-for-PHP_CodeSniffer#naming-conventions
193204
-->
194205
<rule ref="/path/to/standards/Generic/Sniffs/Commenting/TodoSniff.php"/>
195206
<rule ref="../Generic/Sniffs/ControlStructures/InlineControlStructureSniff.php"/>
@@ -479,4 +490,4 @@ The following sample file shows a ruleset.xml file that makes use of selective r
479490
</rule>
480491

481492
</ruleset>
482-
```
493+
```

wiki/Coding-Standard-Tutorial.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
In this tutorial, we will create a new coding standard with a single sniff. Our sniff will prohibit the use of Perl style hash comments.
22

3+
Sniffs need to follow [strict directory layout and naming conventions](https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki/About-Standards-for-PHP_CodeSniffer#naming-conventions).
4+
35
## Creating the Coding Standard Directory
46

57
All sniffs in PHP_CodeSniffer must belong to a coding standard. A coding standard is a directory with a specific sub-directory structure and a `ruleset.xml` file, so we can create one very easily. Let's call our coding standard _MyStandard_. Run the following commands to create the coding standard directory structure:

wiki/_Sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ For everyone
1111

1212
For coding standard creators
1313

14+
[[About Standards for PHP_CodeSniffer]]
1415
[[Annotated Ruleset]]
1516
[[Customisable Sniff Properties]]
1617

0 commit comments

Comments
 (0)