Skip to content
Advertisement

phpcs: How to enforce class inheritance?

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?

Advertisement

Answer

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 MyCodingStandardSniffsClasses;

use PHP_CodeSnifferFixer;
use PHP_CodeSnifferSniffsSniff;
use PHP_CodeSnifferFilesFile;

/**
 * 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
        );
    }
}
User contributions licensed under: CC BY-SA
10 People found this is helpful
Advertisement