phpcs:如何强制执行 class 继承?

phpcs: How to enforce class inheritance?

我想确保某些目录 (src/Controller/CP) 中的所有 class 扩展了其他 class AbstractCPController.

所以

class SupportTicketTagController extends AbstractController

会显示 PHPCS 错误,

class SupportTicketTagController extends AbstractCPController

就好了。

用 PHPCS 可以吗?

我最终创建了自己的 Sniff。它远非完美,但可以作为未来某人的基础:

<?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
        );
    }
}