Commit 8801384c authored by Klaus Purer's avatar Klaus Purer
Browse files

feat(GlobalDrupalSniff): Detect \Drupal calls in service classes (#2847690)

parent 4756f958
Loading
Loading
Loading
Loading
+43 −0
Changes for coder_sniffer/DrupalPractice/Project.php: 43 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -101,6 +101,49 @@ class DrupalPractice_Project
    }//end getInfoFile()


    /**
     * Determines the *.services.yml file in a module.
     *
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
     *
     * @return string|false The Services YML file name or false if it could not
     *   be derived.
     */
    public static function getServicesYmlFile(PHP_CodeSniffer_File $phpcsFile)
    {
        // Cache the services file per file as this might get called often.
        static $cache;

        if (isset($cache[$phpcsFile->getFilename()]) === true) {
            return $cache[$phpcsFile->getFilename()];
        }

        $pathParts = pathinfo($phpcsFile->getFilename());

        // Search for an info file.
        $dir = $pathParts['dirname'];
        do {
            $ymlFiles = glob("$dir/*.services.yml");

            // Go one directory up if we do not find an info file here.
            $dir = dirname($dir);
        } while (empty($ymlFiles) === true && $dir !== dirname($dir));

        // No YML file found, so we give up.
        if (empty($ymlFiles) === true) {
            $cache[$phpcsFile->getFilename()] = false;
            return false;
        }

        // Sort the YML file names and take the shortest info file.
        usort($ymlFiles, array('DrupalPractice_Project', 'compareLength'));
        $ymlFile = $ymlFiles[0];
        $cache[$phpcsFile->getFilename()] = $ymlFile;
        return $ymlFile;

    }//end getServicesYmlFile()


    /**
     * Helper method to sort array values by string length with usort().
     *
+70 −2
Changes for coder_sniffer/DrupalPractice/Sniffs/Objects/GlobalDrupalSniff.php: 70 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -7,8 +7,10 @@
 * @link     http://pear.php.net/package/PHP_CodeSniffer
 */

use Symfony\Component\Yaml\Yaml;

/**
 * Checks that \Drupal::service() and friends is not used in forms or controllers.
 * Checks that \Drupal::service() and friends is not used in forms, controllers, services.
 *
 * @category PHP
 * @package  PHP_CodeSniffer
@@ -83,7 +85,9 @@ class DrupalPractice_Sniffs_Objects_GlobalDrupalSniff implements PHP_CodeSniffer
        $classPtr    = key($tokens[$stackPtr]['conditions']);
        $extendsName = $phpcsFile->findExtendedClassName($classPtr);

        if ($extendsName === false || in_array($extendsName, static::$baseClasses) === false) {
        if (($extendsName === false || in_array($extendsName, static::$baseClasses) === false)
            && $this->isServiceClass($phpcsFile, $classPtr) === false
        ) {
            return;
        }

@@ -93,4 +97,68 @@ class DrupalPractice_Sniffs_Objects_GlobalDrupalSniff implements PHP_CodeSniffer
    }//end process()


    /**
     * Return true if the given class is a Drupal service registered in *.services.yml.
     *
     * @param PHP_CodeSniffer_File $phpcsFile The file being scanned.
     * @param int                  $classPtr  The position of the class declaration
     *                                        in the token stack.
     *
     * @return bool
     */
    private function isServiceClass(PHP_CodeSniffer_File $phpcsFile, $classPtr)
    {
        // Cache the information per file as this might get called often.
        static $cache;

        if (isset($cache[$phpcsFile->getFilename()]) === true) {
            return $cache[$phpcsFile->getFilename()];
        }

        // Get the namespace of the class if there is one.
        $namespacePtr = $phpcsFile->findPrevious(T_NAMESPACE, ($classPtr - 1));
        if ($namespacePtr === false) {
            $cache[$phpcsFile->getFilename()] = false;
            return false;
        }

        $ymlFile = DrupalPractice_Project::getServicesYmlFile($phpcsFile);
        if ($ymlFile === false) {
            $cache[$phpcsFile->getFilename()] = false;
            return false;
        }

        $services = Yaml::parse(file_get_contents($ymlFile));
        if (isset($services['services']) === false) {
            $cache[$phpcsFile->getFilename()] = false;
            return false;
        }

        $nsEnd           = $phpcsFile->findNext(
            [
             T_NS_SEPARATOR,
             T_STRING,
             T_WHITESPACE,
            ],
            ($namespacePtr + 1),
            null,
            true
        );
        $namespace       = trim($phpcsFile->getTokensAsString(($namespacePtr + 1), ($nsEnd - $namespacePtr - 1)));
        $classNameSpaced = ltrim($namespace.'\\'.$phpcsFile->getDeclarationName($classPtr), '\\');

        foreach ($services['services'] as $service) {
            if (isset($service['class']) === true
                && $classNameSpaced === ltrim($service['class'], '\\')
            ) {
                $cache[$phpcsFile->getFilename()] = true;
                return true;
            }
        }

        return false;

    }//end isServiceClass()


}//end class
+15 −0
Changes for coder_sniffer/DrupalPractice/Test/Objects/GlobalDrupalUnitTest.php: 15 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -29,9 +29,24 @@ class DrupalPractice_Sniffs_Objects_GlobalDrupalUnitTest extends CoderSniffUnitT
     */
    protected function getWarningList($testFile)
    {
        switch ($testFile) {
            case 'GlobalDrupalUnitTest.inc':
                return array(6 => 1);
            case 'ExampleService.php':
                return array(14 => 1);
        }
        print $testFile;

    }//end getWarningList()


    /**
     * Returns a list of test files that should be checked.
     *
     * @return array The list of test files.
     */
    protected function getTestFiles() {
        return [__DIR__.'/GlobalDrupalUnitTest.inc', __DIR__.'/src/ExampleService.php'];
    }

}//end class
+17 −0
Changes for coder_sniffer/DrupalPractice/Test/Objects/src/ExampleService.php: 17 added lines, 0 removed lines.
Original line number Diff line number Diff line
<?php

namespace Drupal\testmodule;

/**
 * Some service.
 */
class ExampleService {

  /**
   * Using \Drupal here but it should be injected instead.
   */
  public function test() {
    return \Drupal::configFactory();
  }

}
+3 −0
Changes for coder_sniffer/DrupalPractice/Test/Objects/testmodule.services.yml: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
services:
  testmodule.example:
    class: Drupal\testmodule\ExampleService
Loading