I'm working on a PHP project where we want to ensure that all include, require, include_once, and require_once statements use the __DIR__ constant for path resolution.
I couldn’t find an existing sniff that enforces this rule, so I decided to write my own.
1. Creating the Custom Sniff File
First, create a file that defines your custom sniff. In my project, I placed it in a folder named _dev
(which I later exclude from production). I named the file IncludeUsingDirSniff.php
and used the namespace MyCustom
.
Below is the code for the custom sniff:
<?php declare(strict_types=1);
namespace MyCustom;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that all require/require_once/include/include_once statements use __DIR__ for file paths.
*
* Correct usage:
* require_once __DIR__ . '/../settings.php';
*/
class IncludeUsingDirSniff implements Sniff
{
/**
* Returns an array of token types that this sniff is interested in.
*
* @return int[]
*/
public function register(): array
{
return [
T_REQUIRE,
T_REQUIRE_ONCE,
T_INCLUDE,
T_INCLUDE_ONCE,
];
}
/**
* Processes the token.
*
* Iterates over all tokens of the statement to check if "__DIR__" is present.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the token in the token stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr): void
{
$tokens = $phpcsFile->getTokens();
// Determine the end of the current statement (typically until the semicolon)
$end = $phpcsFile->findEndOfStatement($stackPtr);
if ($end === false) {
$end = count($tokens) - 1;
}
$foundDir = false;
for ($i = $stackPtr; $i <= $end; $i++) {
if (strpos($tokens[$i]['content'], '__DIR__') !== false) {
$foundDir = true;
break;
}
}
if (!$foundDir) {
$error = 'The %s statement must use __DIR__ for the file path.';
$phpcsFile->addError(sprintf($error, $tokens[$stackPtr]['content']), $stackPtr, 'MissingDirUsage');
}
}
}
2. Setting Up the Ruleset
To ensure that PHP CodeSniffer uses my custom sniff, I added a ruleset that explicitly loads my sniff file. I created a ruleset file (e.g., phpcs.xml
) in the project root with the following content:
<?xml version="1.0"?>
<ruleset name="Custom Coding Standard">
<description>
My Stuff
</description>
<!-- the important 2 lines -->
<config name="sniffPaths" value="/var/www/buero/_dev" />
<file name="/var/www/buero/_dev/IncludeUsingDirSniff.php"/>
<rule ref="PSR12">
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
<exclude name="Generic.ControlStructures.InlineControlStructure"/>
</rule>
<arg name="tab-width" value="4"/>
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array">
<element key="var_dump" value="true"/>
<element key="print_r" value="true"/>
<element key="echo" value="true"/>
</property>
</properties>
</rule>
</ruleset>
Key Points:
3. Running PHP CodeSniffer
Now, when running PHP CodeSniffer from the project root:
$phpcs --standard=_dev/phpcs.xml /var/www/buero
ERROR: Referenced sniff "MyCustom.IncludeUsingDirSniff" does not exist
What am I doing wrong?
I'm working on a PHP project where we want to ensure that all include, require, include_once, and require_once statements use the __DIR__ constant for path resolution.
I couldn’t find an existing sniff that enforces this rule, so I decided to write my own.
1. Creating the Custom Sniff File
First, create a file that defines your custom sniff. In my project, I placed it in a folder named _dev
(which I later exclude from production). I named the file IncludeUsingDirSniff.php
and used the namespace MyCustom
.
Below is the code for the custom sniff:
<?php declare(strict_types=1);
namespace MyCustom;
use PHP_CodeSniffer\Files\File;
use PHP_CodeSniffer\Sniffs\Sniff;
/**
* Checks that all require/require_once/include/include_once statements use __DIR__ for file paths.
*
* Correct usage:
* require_once __DIR__ . '/../settings.php';
*/
class IncludeUsingDirSniff implements Sniff
{
/**
* Returns an array of token types that this sniff is interested in.
*
* @return int[]
*/
public function register(): array
{
return [
T_REQUIRE,
T_REQUIRE_ONCE,
T_INCLUDE,
T_INCLUDE_ONCE,
];
}
/**
* Processes the token.
*
* Iterates over all tokens of the statement to check if "__DIR__" is present.
*
* @param File $phpcsFile The file being scanned.
* @param int $stackPtr The position of the token in the token stack.
*
* @return void
*/
public function process(File $phpcsFile, $stackPtr): void
{
$tokens = $phpcsFile->getTokens();
// Determine the end of the current statement (typically until the semicolon)
$end = $phpcsFile->findEndOfStatement($stackPtr);
if ($end === false) {
$end = count($tokens) - 1;
}
$foundDir = false;
for ($i = $stackPtr; $i <= $end; $i++) {
if (strpos($tokens[$i]['content'], '__DIR__') !== false) {
$foundDir = true;
break;
}
}
if (!$foundDir) {
$error = 'The %s statement must use __DIR__ for the file path.';
$phpcsFile->addError(sprintf($error, $tokens[$stackPtr]['content']), $stackPtr, 'MissingDirUsage');
}
}
}
2. Setting Up the Ruleset
To ensure that PHP CodeSniffer uses my custom sniff, I added a ruleset that explicitly loads my sniff file. I created a ruleset file (e.g., phpcs.xml
) in the project root with the following content:
<?xml version="1.0"?>
<ruleset name="Custom Coding Standard">
<description>
My Stuff
</description>
<!-- the important 2 lines -->
<config name="sniffPaths" value="/var/www/buero/_dev" />
<file name="/var/www/buero/_dev/IncludeUsingDirSniff.php"/>
<rule ref="PSR12">
<exclude name="Generic.WhiteSpace.DisallowTabIndent"/>
<exclude name="Generic.ControlStructures.InlineControlStructure"/>
</rule>
<arg name="tab-width" value="4"/>
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array">
<element key="var_dump" value="true"/>
<element key="print_r" value="true"/>
<element key="echo" value="true"/>
</property>
</properties>
</rule>
</ruleset>
Key Points:
3. Running PHP CodeSniffer
Now, when running PHP CodeSniffer from the project root:
$phpcs --standard=_dev/phpcs.xml /var/www/buero
ERROR: Referenced sniff "MyCustom.IncludeUsingDirSniff" does not exist
What am I doing wrong?
Share Improve this question edited 2 days ago hakre 198k55 gold badges446 silver badges854 bronze badges Recognized by PHP Collective asked 2 days ago Juergen SchulzeJuergen Schulze 1,65226 silver badges34 bronze badges1 Answer
Reset to default 0The sniff must be placed into a subdirectory in the ./sniffs
sub-directory of your ruleset directory ./_dev/MyCustom
:
./MyCustom
is missing (optional, you could re-use./_dev
for it but then don't complain when you need to clean that up later, for the rest of the answer I'll use it)- The directory
_dev/MyCustom/Sniffs
does not exists. (ref) - Read the rest of that page, no need to copy it all over here. There is another sub-directory to create (ref) etc. pp.
As long as you see the diagnostic messages, they are correct. They might not be well understood, but phpcs is not lying. Use Xdebug on the CLI for your benefit before you pull out any of your hairs (XDEBUG_MODE=debug XDEBUG_SESSION=1 phpcs ...
).