Skip to main content
Drupal API
User account menu
  • Log in

Breadcrumb

  1. Drupal Core 11.1.x
  2. ForbiddenClassesSniff.php

class ForbiddenClassesSniff

Hierarchy

  • class \SlevomatCodingStandard\Sniffs\PHP\ForbiddenClassesSniff implements \PHP_CodeSniffer\Sniffs\Sniff

Expanded class hierarchy of ForbiddenClassesSniff

File

vendor/slevomat/coding-standard/SlevomatCodingStandard/Sniffs/PHP/ForbiddenClassesSniff.php, line 32

Namespace

SlevomatCodingStandard\Sniffs\PHP
View source
class ForbiddenClassesSniff implements Sniff {
    public const CODE_FORBIDDEN_CLASS = 'ForbiddenClass';
    public const CODE_FORBIDDEN_PARENT_CLASS = 'ForbiddenParentClass';
    public const CODE_FORBIDDEN_INTERFACE = 'ForbiddenInterface';
    public const CODE_FORBIDDEN_TRAIT = 'ForbiddenTrait';
    
    /** @var array<string, (string|null)> */
    public $forbiddenClasses = [];
    
    /** @var array<string, (string|null)> */
    public $forbiddenExtends = [];
    
    /** @var array<string, (string|null)> */
    public $forbiddenInterfaces = [];
    
    /** @var array<string, (string|null)> */
    public $forbiddenTraits = [];
    
    /** @var list<string> */
    private static $keywordReferences = [
        'self',
        'parent',
        'static',
    ];
    
    /**
     * @return array<int, (int|string)>
     */
    public function register() : array {
        $searchTokens = [];
        if (count($this->forbiddenClasses) > 0) {
            $this->forbiddenClasses = self::normalizeInputOption($this->forbiddenClasses);
            $searchTokens[] = T_NEW;
            $searchTokens[] = T_DOUBLE_COLON;
        }
        if (count($this->forbiddenExtends) > 0) {
            $this->forbiddenExtends = self::normalizeInputOption($this->forbiddenExtends);
            $searchTokens[] = T_EXTENDS;
        }
        if (count($this->forbiddenInterfaces) > 0) {
            $this->forbiddenInterfaces = self::normalizeInputOption($this->forbiddenInterfaces);
            $searchTokens[] = T_IMPLEMENTS;
        }
        if (count($this->forbiddenTraits) > 0) {
            $this->forbiddenTraits = self::normalizeInputOption($this->forbiddenTraits);
            $searchTokens[] = T_USE;
        }
        return $searchTokens;
    }
    
    /**
     * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
     * @param int $tokenPointer
     */
    public function process(File $phpcsFile, $tokenPointer) : void {
        $tokens = $phpcsFile->getTokens();
        $token = $tokens[$tokenPointer];
        $nameTokens = array_merge(TokenHelper::getNameTokenCodes(), TokenHelper::$ineffectiveTokenCodes);
        if ($token['code'] === T_IMPLEMENTS || $token['code'] === T_USE && UseStatementHelper::isTraitUse($phpcsFile, $tokenPointer)) {
            $endTokenPointer = TokenHelper::findNext($phpcsFile, [
                T_SEMICOLON,
                T_OPEN_CURLY_BRACKET,
            ], $tokenPointer);
            $references = $this->getAllReferences($phpcsFile, $tokenPointer, $endTokenPointer);
            if ($token['code'] === T_IMPLEMENTS) {
                $this->checkReferences($phpcsFile, $tokenPointer, $references, $this->forbiddenInterfaces);
            }
            else {
                // Fixer does not work when traits contains aliases
                $this->checkReferences($phpcsFile, $tokenPointer, $references, $this->forbiddenTraits, $tokens[$endTokenPointer]['code'] !== T_OPEN_CURLY_BRACKET);
            }
        }
        elseif (in_array($token['code'], [
            T_NEW,
            T_EXTENDS,
        ], true)) {
            $endTokenPointer = TokenHelper::findNextExcluding($phpcsFile, $nameTokens, $tokenPointer + 1);
            $references = $this->getAllReferences($phpcsFile, $tokenPointer, $endTokenPointer);
            $this->checkReferences($phpcsFile, $tokenPointer, $references, $token['code'] === T_NEW ? $this->forbiddenClasses : $this->forbiddenExtends);
        }
        elseif ($token['code'] === T_DOUBLE_COLON && !$this->isTraitsConflictResolutionToken($token)) {
            $startTokenPointer = TokenHelper::findPreviousExcluding($phpcsFile, $nameTokens, $tokenPointer - 1);
            $references = $this->getAllReferences($phpcsFile, $startTokenPointer, $tokenPointer);
            $this->checkReferences($phpcsFile, $tokenPointer, $references, $this->forbiddenClasses);
        }
    }
    
    /**
     * @param list<array{fullyQualifiedName: string, startPointer: int|null, endPointer: int|null}> $references
     * @param array<string, (string|null)> $forbiddenNames
     */
    private function checkReferences(File $phpcsFile, int $tokenPointer, array $references, array $forbiddenNames, bool $isFixable = true) : void {
        $token = $phpcsFile->getTokens()[$tokenPointer];
        $details = [
            T_NEW => [
                'class',
                self::CODE_FORBIDDEN_CLASS,
            ],
            T_DOUBLE_COLON => [
                'class',
                self::CODE_FORBIDDEN_CLASS,
            ],
            T_EXTENDS => [
                'as a parent class',
                self::CODE_FORBIDDEN_PARENT_CLASS,
            ],
            T_IMPLEMENTS => [
                'interface',
                self::CODE_FORBIDDEN_INTERFACE,
            ],
            T_USE => [
                'trait',
                self::CODE_FORBIDDEN_TRAIT,
            ],
        ];
        foreach ($references as $reference) {
            if (!array_key_exists($reference['fullyQualifiedName'], $forbiddenNames)) {
                continue;
            }
            $alternative = $forbiddenNames[$reference['fullyQualifiedName']];
            [
                $nameType,
                $code,
            ] = $details[$token['code']];
            if ($alternative === null) {
                $phpcsFile->addError(sprintf('Usage of %s %s is forbidden.', $reference['fullyQualifiedName'], $nameType), $reference['startPointer'], $code);
            }
            elseif (!$isFixable) {
                $phpcsFile->addError(sprintf('Usage of %s %s is forbidden, use %s instead.', $reference['fullyQualifiedName'], $nameType, $alternative), $reference['startPointer'], $code);
            }
            else {
                $fix = $phpcsFile->addFixableError(sprintf('Usage of %s %s is forbidden, use %s instead.', $reference['fullyQualifiedName'], $nameType, $alternative), $reference['startPointer'], $code);
                if (!$fix) {
                    continue;
                }
                $phpcsFile->fixer
                    ->beginChangeset();
                FixerHelper::change($phpcsFile, $reference['startPointer'], $reference['endPointer'], $alternative);
                $phpcsFile->fixer
                    ->endChangeset();
            }
        }
    }
    
    /**
     * @param array<string, array<int, int|string>|int|string> $token
     */
    private function isTraitsConflictResolutionToken(array $token) : bool {
        return is_array($token['conditions']) && array_pop($token['conditions']) === T_USE;
    }
    
    /**
     * @return list<array{fullyQualifiedName: string, startPointer: int|null, endPointer: int|null}>
     */
    private function getAllReferences(File $phpcsFile, int $startPointer, int $endPointer) : array {
        // Always ignore first token
        $startPointer++;
        $references = [];
        while ($startPointer < $endPointer) {
            $nextComma = TokenHelper::findNext($phpcsFile, [
                T_COMMA,
            ], $startPointer + 1);
            $nextSeparator = min($endPointer, $nextComma ?? PHP_INT_MAX);
            $reference = ReferencedNameHelper::getReferenceName($phpcsFile, $startPointer, $nextSeparator - 1);
            if (strlen($reference) !== 0 && !in_array(strtolower($reference), self::$keywordReferences, true)) {
                $references[] = [
                    'fullyQualifiedName' => NamespaceHelper::resolveClassName($phpcsFile, $reference, $startPointer),
                    'startPointer' => TokenHelper::findNextEffective($phpcsFile, $startPointer, $endPointer),
                    'endPointer' => TokenHelper::findPreviousEffective($phpcsFile, $nextSeparator - 1, $startPointer),
                ];
            }
            $startPointer = $nextSeparator + 1;
        }
        return $references;
    }
    
    /**
     * @param array<string, (string|null)> $option
     * @return array<string, (string|null)>
     */
    private static function normalizeInputOption(array $option) : array {
        $forbiddenClasses = [];
        foreach ($option as $forbiddenClass => $alternative) {
            $forbiddenClasses[self::normalizeClassName($forbiddenClass)] = self::normalizeClassName($alternative);
        }
        return $forbiddenClasses;
    }
    private static function normalizeClassName(?string $typeName) : ?string {
        if ($typeName === null || strlen($typeName) === 0 || strtolower($typeName) === 'null') {
            return null;
        }
        return NamespaceHelper::getFullyQualifiedTypeName($typeName);
    }

}

Members

Title Sort descending Modifiers Object type Summary Overriden Title
ForbiddenClassesSniff::$forbiddenClasses public property @var array&lt;string, (string|null)&gt;
ForbiddenClassesSniff::$forbiddenExtends public property @var array&lt;string, (string|null)&gt;
ForbiddenClassesSniff::$forbiddenInterfaces public property @var array&lt;string, (string|null)&gt;
ForbiddenClassesSniff::$forbiddenTraits public property @var array&lt;string, (string|null)&gt;
ForbiddenClassesSniff::$keywordReferences private static property @var list&lt;string&gt;
ForbiddenClassesSniff::checkReferences private function *
ForbiddenClassesSniff::CODE_FORBIDDEN_CLASS public constant
ForbiddenClassesSniff::CODE_FORBIDDEN_INTERFACE public constant
ForbiddenClassesSniff::CODE_FORBIDDEN_PARENT_CLASS public constant
ForbiddenClassesSniff::CODE_FORBIDDEN_TRAIT public constant
ForbiddenClassesSniff::getAllReferences private function *
ForbiddenClassesSniff::isTraitsConflictResolutionToken private function *
ForbiddenClassesSniff::normalizeClassName private static function
ForbiddenClassesSniff::normalizeInputOption private static function *
ForbiddenClassesSniff::process public function * @phpcsSuppress SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingNativeTypeHint
*
Overrides Sniff::process
ForbiddenClassesSniff::register public function * Overrides Sniff::register
RSS feed
Powered by Drupal