I want to make sure all classes in some dir (src/Controller/CP
) extend some other class AbstractCPController
.
So that
class SupportTicketTagController extends AbstractController
would show PHPCS error, and
class SupportTicketTagController extends AbstractCPController
would be fine.
Is that possible with PHPCS?
I ended up creating my own Sniff. Its far from perfect but may serve as a base for somebody in the future:
<?php
declare(strict_types=1);
namespace MyCodingStandard\Sniffs\Classes;
use PHP_CodeSniffer\Fixer;
use PHP_CodeSniffer\Sniffs\Sniff;
use PHP_CodeSniffer\Files\File;
/**
* Sniff for catching classes not marked as abstract or final
*/
final class ClassInheritanceSniff implements Sniff
{
private ?Fixer $fixer;
private $position;
public function register(): array
{
return [T_CLASS];
}
public function process(File $file, $position): void
{
$this->fixer = $file->fixer;
$this->position = $position;
$tokens = $file->getTokens();
$className = $tokens[$file->findNext(T_STRING, $position + 1)]['content'];
if ($className === 'AbstractCPController') {
return;
}
$curlyOpenLine = $file->findNext(T_OPEN_CURLY_BRACKET, $position + 1);
$extendsLine = $file->findNext(T_EXTENDS, $position + 1, $curlyOpenLine - 1);
$implementsLine = $file->findNext(T_IMPLEMENTS, $position + 1, $curlyOpenLine - 1);
$extendedClasses = [];
$implementedInterfaces = [];
if ($extendsLine) {
$extendsLineMax = $curlyOpenLine - 1;
if ($implementsLine > $extendsLine) {
$extendsLineMax = $implementsLine - 1;
}
$lastClassName = '';
for ($i = $extendsLine + 1; $i < $extendsLineMax; $i++) {
if ($tokens[$i]['code'] == T_STRING || $tokens[$i]['code'] == T_NS_SEPARATOR) {
$lastClassName .= $tokens[$i]['content'];
} else {
if ($lastClassName) {
$extendedClasses[] = $lastClassName;
$lastClassName = '';
}
}
}
if ($lastClassName) {
$extendedClasses[] = $lastClassName;
}
}
if ($implementsLine) {
$implementsLineMax = $curlyOpenLine - 1;
if ($extendsLine > $implementsLine) {
$implementsLineMax = $extendsLine - 1;
}
$lastClassName = '';
for ($i = $implementsLine + 1; $i < $implementsLineMax; $i++) {
if ($tokens[$i]['code'] == T_STRING || $tokens[$i]['code'] == T_NS_SEPARATOR) {
$lastClassName .= $tokens[$i]['content'];
} else {
if ($lastClassName) {
$implementedInterfaces[] = $lastClassName;
$lastClassName = '';
}
}
}
if ($lastClassName) {
$implementedInterfaces[] = $lastClassName;
}
}
foreach ($extendedClasses as $class) {
if (str_ends_with($class, 'AbstractCPController')) {
return;
}
}
$file->addError(
'All CP Controller classes should extend from AbstractCPController',
$position - 1,
self::class
);
}
}