Skip to content
[READ-ONLY] 40+ Coding Standard checkers for PHP projects with focus on Clean Architecture
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
config drop BetterPhpDocParser .yaml config references Mar 9, 2019
packages/TokenRunner
src add 'allow_classes' option to UnusedPublicMethodSniff + apply Mar 16, 2019
tests
.gitattributes coveralls: drop for packages, too much detailed May 28, 2017
.gitignore rename /src to more propper name /packages [closes #3] Oct 26, 2016
.travis.yml
LICENSE
README.md
composer.json
phpunit.xml

README.md

Coding Standard

Build Status Downloads

Set of PHP_CodeSniffer Sniffs and PHP-CS-Fixer Fixers used by Symplify projects.

They run best with EasyCodingStandard.

Install

composer require symplify/coding-standard --dev

Rules Overview

  • Rules with 🔧 are configurable.

Make sure That @param, @var, @return and @throw Types Exist

services:
    Symplify\CodingStandard\Sniffs\Commenting\AnnotationTypeExistsSniff: ~

<?php

class SomeClass
{
    /**
     * @var NonExistingClass
     */
    private $property;
}

👍

<?php

class SomeClass
{
    /**
     * @var ExistingClass
     */
    private $property;
}

Use Unique Class Short Names

<?php

namespace App;

class Finder
{
}
<?php

namespace App\Entity;

class Finder
{
}

👍

 <?php

 namespace App\Entity;

-class Finder
+class EntityFinder
 {
 }

Do you want skip some classes? Configure it:

# ecs.yml
services:
    Symplify\CodingStandard\Sniffs\Architecture\DuplicatedClassShortNameSniff:
        allowed_class_names:
            - 'Request'
            - 'Response'

Make @param, @return and @var Format United

services:
    Symplify\CodingStandard\Fixer\Commenting\ParamReturnAndVarTagMalformsFixer: ~
 <?php

 /**
- * @param $name string
+ * @param string $name
- * @return int $value
+ * @return int
  */
 function someFunction($name)
 {
 }

 class SomeClass
 {
     /**
-     * @var int $property
+     * @var int
      */
     private $property;
 }

-/* @var int $value */
+/** @var int $value */
 $value = 5;

-/** @var $value int */
+/** @var int $value */
 $value = 5;

Remove // end of ... Legacy Comments

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Commenting\RemoveEndOfFunctionCommentFixer: ~
 <?php

 function someFunction()
 {

-} // end of someFunction
+}

Order Private Methods by Their Use Order

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Order\PrivateMethodOrderByUseFixer: ~

<?php

class SomeClass
{
    public function run()
    {
        $this->call1();
        $this->call2();
    }

    private function call2()
    {
    }

    private function call1()
    {
    }
}

👍

<?php

class SomeClass
{
    public function run()
    {
        $this->call1();
        $this->call2();
    }

    private function call1()
    {
    }

    private function call2()
    {
    }
}

Order Properties From Simple to Complex

Properties are ordered by visibility first, then by complexity.

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Order\PropertyOrderByComplexityFixer: ~

<?php

final class SomeFixer
{
    /**
     * @var string
     */
    private $name;

    /**
     * @var Type
     */
    private $service;

    /**
     * @var int
     */
    private $price;
}

👍

<?php

final class SomeFixer
{
    /**
     * @var int
     */
    private $price;

    /**
     * @var string
     */
    private $name;

    /**
     * @var Type
     */
    private $service;
}

Prefer Another Class

# ecs.yml
services:
    Symplify\CodingStandard\Sniffs\Architecture\PreferredClassSniff:
        oldToPreferredClasses:
            DateTime: 'Nette\Utils\DateTime'

<?php

$dateTime = new DateTime('now');

👍

<?php

$dateTime = new Nette\Utils\DateTime('now');

Indexed PHP arrays should have 1 item per line

-$friends = [1 => 'Peter', 2 => 'Paul'];
+$friends = [
+    1 => 'Peter',
+    2 => 'Paul'
+];

There should not be empty PHPDoc blocks

Just like PhpCsFixer\Fixer\Phpdoc\NoEmptyPhpdocFixer, but this one removes all doc block lines.

-/**
- */
 public function someMethod()
 {
 }

Block comment should not have 2 empty lines in a row

 /**
  * @param int $value
  *
- *
  * @return array
  */
 public function setCount($value)
 {
 }

Consistent Delimiter in Regular Expression

-preg_match('~pattern~', $value);
+preg_match('#pattern#', $value);

-preg_match("~pattern~d", $value);
+preg_match("#pattern#d", $value);

-Nette\Utils\Strings::match($value, '/pattern/');
+Nette\Utils\Strings::match($value, '#pattern#');

Do you want another char than #? Configure it:

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\ControlStructure\PregDelimiterFixer:
        delimiter: '_' # default "#"

Include/Require should be followed by absolute path

-require 'vendor/autoload.php';
+require __DIR__.'/vendor/autoload.php';

Parameters, arguments and array items should be on the same/standalone line to fit line length

 class SomeClass
 {
-    public function someMethod(SuperLongArguments $superLongArguments, AnotherLongArguments $anotherLongArguments, $oneMore)
+    public function someMethod(
+        SuperLongArguments $superLongArguments,
+        AnotherLongArguments $anotherLongArguments,
+        $oneMore
+    )
     {
     }

-    public function someOtherMethod(
-        ShortArgument $shortArgument,
-        $oneMore
-    ) {
+    public function someOtherMethod(ShortArgument $shortArgument, $oneMore) {
     }
 }
  • Are 120 characters too long for you?
  • Do you want to break longs lines but not inline short lines or vice versa?

Change it:

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\LineLength\LineLengthFixer:
        max_line_length: 100 # default: 120
        break_long_lines: true # default: true
        inline_short_lines: false # default: true

Property name should match its key, if possible

-public function __construct(EntityManagerInterface $eventManager)
+public function __construct(EntityManagerInterface $entityManager)
 {
-    $this->eventManager = $eventManager;
+    $this->entityManager = $entityManager;
 }

This checker ignores few system classes like std* or Spl* by default. In case want to skip more classes, you can configure it:

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer:
        extra_skipped_classes:
            - 'MyApp*' # accepts anything like fnmatch

Exception name should match its type, if possible

 try {
-} catch (SomeException $typoException) {
+} catch (SomeException $someException) {
-    $typeException->getMessage();
+    $someException->getMessage();
 }

Public Methods Should have Specific Order by Interface/Parent Class

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Order\MethodOrderByTypeFixer:
        method_order_by_type:
            Rector\Contract\Rector\PhpRectorInterface:
                - 'getNodeTypes'
                - 'refactor'

 final class SomeRector implements PhpRectorInterface
 {
-    public function refactor()
+    public function getNodeTypes()
     {
-        // refactoring
+        return ['SomeType'];
     }
-
-    public function getNodeTypes()
+    public function refactor(): void
     {
-        return ['SomeType'];
+        // refactoring
     }
 }

::class references should be used over string for classes and interfaces

-$className = 'DateTime';
+$className = DateTime::class;

This checker takes only existing classes by default. In case want to check another code not loaded by local composer, you can configure it:

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer:
        class_must_exist: false # true by default

Do you want to allow some classes to be in string format?

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer:
        allow_classes:
            - 'SomeClass'

Array property should have default value, to prevent undefined array issues

 class SomeClass
 {
     /**
      * @var string[]
      */
-    public $apples;
+    public $apples = [];

     public function run()
     {
         foreach ($this->apples as $mac) {
             // ...
         }
     }
 }

Strict types declaration has to be followed by empty line

 <?php declare(strict_types=1);
+
 namespace SomeNamespace;

Non-abstract class that implements interface should be final

Except for Doctrine entities, they cannot be final.

-class SomeClass implements SomeInterface
+final class SomeClass implements SomeInterface
 {
 }

In case want check this only for specific interfaces, you can configure them:

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\Solid\FinalInterfaceFixer:
        only_interfaces:
            - 'Symfony\Component\EventDispatcher\EventSubscriberInterface'
            - 'Nette\Application\IPresenter'

Block comment should be used instead of one liner

 class SomeClass
 {
-    /** @var int */
+    /**
+     * @var int
+     */
     public $count;
 }

Use explicit and informative exception names over generic ones

<?php

throw new RuntimeException('...');

👍

<?php

throw new FileNotFoundException('...');

Class "X" cannot be parent class. Use composition over inheritance instead.

# ecs.yml
services:
    Symplify\CodingStandard\Sniffs\CleanCode\ForbiddenParentClassSniff:
        forbiddenParentClasses:
            - 'Doctrine\ORM\EntityRepository'
            # again, you can use fnmatch() pattern
            - '*\AbstractController'

<?php

use Doctrine\ORM\EntityRepository;

final class ProductRepository extends EntityRepository
{
}

👍

<?php

use Doctrine\ORM\EntityRepository;

final class ProductRepository
{
    /**
     * @var EntityRepository
     */
    private $entityRepository;

    public function __construct(EntityRepository $entityRepository)
    {
        $this->entityRepository = $entityRepository;
    }
}

Use explicit return values over magic "&$variable" reference

<?php

function someFunction(&$var)
{
    $var + 1;
}

👍

<?php

function someFunction($var)
{
    return $var + 1;
}

Use services and constructor injection over static method

<?php

class SomeClass
{
    public static function someFunction()
    {
    }
}

👍

<?php

class SomeClass
{
    public function someFunction()
    {
    }
}

Constant should have docblock comment

class SomeClass
{
    private const EMPATH_LEVEL = 55;
}

👍

<?php

class SomeClass
{
    /**
     * @var int
     */
    private const EMPATH_LEVEL = 55;
}

Use per line assign instead of multiple ones

<?php

$value = $anotherValue = [];

👍

<?php

$value = [];
$anotherValue = [];

Prefer sprintf() over multiple concats ( . ).

<?php

return 'Class ' . $oldClass . ' was removed from ' . $file . '. Use ' . self::class . " instead';

👍

<?php

return sprintf('Class "%s" was removed from "%s". Use "%s" instead', $oldClass, $file, self::class);

Is 2 . too strict? Just configure it:

# ecs.yml
services:
    Symplify\CodingStandard\Sniffs\ControlStructure\SprintfOverContactSniff:
        maxConcatCount: 4 # "3" by default

There should not be comments with valid code

<?php

// $file = new File;
// $directory = new Diretory([$file]);

Debug functions should not be left in the code

<?php

d($value);
dd($value);
dump($value);
var_dump($value);

Use service and constructor injection rather than instantiation with new

<?php

class SomeController
{
   public function renderEdit(array $data)
   {
        $database = new Database;
        $database->save($data);
   }
}

👍

<?php

class SomeController
{
   public function renderEdit(array $data)
   {
        $this->database->save($data);
   }
}

This checkers ignores by default some classes, see $allowedClasses property.

In case want to exclude more classes, you can configure it with class or pattern using fnmatch:

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\DependencyInjection\NoClassInstantiationSniff:
        extraAllowedClasses:
            - 'PhpParser\Node\*'

Doctrine entities are skipped as well. You can disable that by:

# ecs.yml
services:
    Symplify\CodingStandard\Fixer\DependencyInjection\NoClassInstantiationSniff:
        includeEntities: true

Abstract class should have prefix "Abstract"

<?php

abstract class SomeClass
{
}

👍

<?php

abstract class AbstractSomeClass
{
}

Class should have suffix by parent class/interface

<?php

class Some extends Command
{
}

👍

<?php

class SomeCommand extends Command
{
}

This checker check few names by default. But if you need, you can configure it:

# ecs.yml
services:
    Symplify\CodingStandard\Sniffs\Naming\ClassNameSuffixByParentSniff:
        parentTypesToSuffixes:
            # defaults
            - 'Command'
            - 'Controller'
            - 'Repository'
            - 'Presenter'
            - 'Request'
            - 'Response'
            - 'EventSubscriber'
            - 'FixerInterface'
            - 'Sniff'
            - 'Exception'
            - 'Handler'

Or keep all defaults values by using extra_parent_types_to_suffixes:

# ecs.yml
services:
    Symplify\CodingStandard\Sniffs\Naming\ClassNameSuffixByParentSniff:
        extraParentTypesToSuffixes:
            - 'ProviderInterface'

It also covers Interface suffix as well, e.g EventSubscriber checks for EventSubscriberInterface as well.


Interface should have suffix "Interface"

<?php

interface Some
{
}

👍

<?php

interface SomeInterface
{
}

Trait should have suffix "Trait"

<?php

trait Some
{
}

👍

<?php

trait SomeTrait
{
}

Brave Checkers

Possible Unused Public Method

<?php

class SomeClass
{
    public function usedMethod()
    {
    }

    public function unusedMethod()
    {
    }
}

$someObject = new SomeClass;
$someObject->usedMethod();

👍

<?php

class SomeClass
{
    public function usedMethod()
    {
    }
}

$someObject = new SomeClass;
$someObject->usedMethod();

Do you have public methods used only byr 3rd party? Just skip them:

# ecs.yaml
services:
    Symplify\CodingStandard\Sniffs\DeadCode\UnusedPublicMethodSniff:
        allow_classes:
            - 'Symplify\Autodiscovery\Discovery'
            - 'Symplify\FlexLoader\Flex\FlexLoader'

Contributing

Open an issue or send a pull-request to main repository.

You can’t perform that action at this time.