色んなPHP CS Fixer達を紹介します。
- Symplify/CodingStandard
- object-calisthenics/phpcs-calisthenics-rules
- bmitch/Codor
- Slamdunk/php-cs-fixer-extensions
- PedroTroller/PhpCSFixer-Custom-Fixers
- kubawerlos/php-cs-fixer-custom-fixers
CodingStandard
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 only contain useful information about types
/**
- * @param int $value
- * @param $anotherValue
- * @param SomeType $someService
- * @return array
*/
public function setCount(int $value, $anotherValue, SomeType $someService): array
{
}
This checker keeps 'mixed' and 'object' and other types by default. But if you need, you can configure it:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\Commenting\RemoveUselessDocBlockFixer:
useless_types: ['mixed', 'object'] # [] by default
Block comment should not have 2 empty lines in a row
/**
* @param int $value
*
- *
* @return array
*/
public function setCount($value)
{
}
Include/Require should be followed by absolute path
-require 'vendor/autoload.php';
+require __DIR__.'/vendor/autoload.php';
Types should not be referenced via a fully/partially qualified name, but via a use statement
namespace SomeNamespace;
+use AnotherNamespace\AnotherType;
class SomeClass
{
public function someMethod()
{
- return new \AnotherNamespace\AnotherType;
+ return new AnotherType;
}
}
This checker imports single name classes like \Twig_Extension
or \SplFileInfo
by default. But if you need, you can configure it:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer:
allow_single_names: true # false by default
You can also configure to check /** @var Namespaced\DocBlocks */
as well:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\Import\ImportNamespacedNameFixer:
include_doc_blocks: true # false by default
And what about duplicate class name? They are uniquized by vendor name:
<?php declare(strict_types=1);
namespace SomeNamespace;
use Nette\Utils\Finder as NetteFinder;
use Symfony\Finder\Finder;
class SomeClass
{
public function create(NetteFinder $someClass)
{
return new Finder;
}
}
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:
# easy-coding-standard.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
Magic PHP methods (__*()
) should respect their casing form
class SomeClass
{
- public function __CONSTRUCT()
+ public function __construct()
{
}
}
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:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer:
extra_skipped_classes:
- 'MyApp*' # accepts anything like fnmatch
::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:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\Php\ClassStringToClassConstantFixer:
class_must_exist: false # true by default
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:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\Solid\FinalInterfaceFixer:
onlyInterfaces:
- '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
throw new RuntimeException('...');
throw new FileNotFoundException('...');
Use explicit return values over magic "&$variable" reference
function someFunction(&$var)
{
$var + 1;
}
function someFunction($var)
{
return $var + 1;
}
Use services and constructor injection over static method
class SomeClass
{
public static function someFunction()
{
}
}
class SomeClass
{
public function someFunction()
{
}
}
Constant should have docblock comment
class SomeClass
{
private const EMPATH_LEVEL = 55;
}
class SomeClass
{
/**
* @var int
*/
private const EMPATH_LEVEL = 55;
}
Prefer sprintf()
over multiple concats ( . ).
return 'Class ' . $oldClass . ' was removed from ' . $file . '. Use ' . self::class . " instead';;
return sprintf('Class "%s" was removed from "%s". Use "%s" instead', $oldClass, $file, self::class);
Is 2 .
too strict? Just configure it:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Sniffs\ControlStructure\SprintfOverContactSniff:
maxConcatCount: 4 # "3" by default
There should not be comments with valid code
// $file = new File;
// $directory = new Diretory([$file]);
Debug functions should not be left in the code
dump($value);
Use service and constructor injection rather than instantiation with new
class SomeController
{
public function renderEdit(array $data)
{
$database = new Database;
$database->save($data);
}
}
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
:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\DependencyInjection\NoClassInstantiationSniff:
extraAllowedClasses:
- 'PhpParser\Node\*'
Doctrine entities are skipped as well. You can disable that by:
# easy-coding-standard.yml
services:
Symplify\CodingStandard\Fixer\DependencyInjection\NoClassInstantiationSniff:
includeEntities: true
Abstract class should have prefix "Abstract"
abstract class SomeClass
{
}
abstract class AbstractSomeClass
{
}
Class should have suffix by parent class/interface
class Some extends Command
{
}
class SomeCommand extends Command
{
}
This checker check few names by default. But if you need, you can configure it:
# easy-coding-standard.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
:
# easy-coding-standard.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"
interface Some
{
}
interface SomeInterface
{
}
Trait should have suffix "Trait"
trait Some
{
}
trait SomeTrait
{
}
Brave Checkers
Possible Unused Public Method
-
class:
Symplify\CodingStandard\Sniffs\DeadCode\UnusedPublicMethodSniff
-
Requires ECS due double run feature.
class SomeClass
{
public function usedMethod()
{
}
public function unusedMethod()
{
}
}
$someObject = new SomeClass;
$someObject->usedMethod();
class SomeClass
{
public function usedMethod()
{
}
}
$someObject = new SomeClass;
$someObject->usedMethod();
phpcs-calisthenics-rules
1. Only X
Level of Indentation per Method
foreach ($sniffGroups as $sniffGroup) {
foreach ($sniffGroup as $sniffKey => $sniffClass) {
if (! $sniffClass instanceof Sniff) {
throw new InvalidClassTypeException;
}
}
}
foreach ($sniffGroups as $sniffGroup) {
$this->ensureIsAllInstanceOf($sniffGroup, Sniff::class);
}
// ...
private function ensureIsAllInstanceOf(array $objects, string $type)
{
// ...
}
Use Just This One?
--sniffs=ObjectCalisthenics.Metrics.MaxNestingLevel
# easy-coding-standard.yml
services:
ObjectCalisthenics\Sniffs\Metrics\MaxNestingLevelSniff: ~
Configurable
2. Do Not Use "else" Keyword
if ($status === self::DONE) {
$this->finish();
} else {
$this->advance();
}
if ($status === self::DONE) {
$this->finish();
return;
}
$this->advance();
Use Just This One?
--sniffs=ObjectCalisthenics.ControlStructures.NoElseSniff
# easy-coding-standard.yml
services:
ObjectCalisthenics\Sniffs\ControlStructures\NoElseSniff: ~
5. Use Only One Object Operator (->
) per Line
$this->container->getBuilder()->addDefinition(SniffRunner::class);
$containerBuilder = $this->getContainerBuilder();
$containerBuilder->addDefinition(SniffRunner::class);
Use Just This One?
--sniffs=ObjectCalisthenics.CodeAnalysis.OneObjectOperatorPerLine
# easy-coding-standard.yml
services:
ObjectCalisthenics\Sniffs\CodeAnalysis\OneObjectOperatorPerLineSniff: ~
Configurable
6. Do not Abbreviate
This is related to class, trait, interface, constant, function and variable names.
class EM
{
// ...
}
class EntityMailer
{
// ...
}
Use Just This One?
--sniffs=ObjectCalisthenics.NamingConventions.ElementNameMinimalLength
# easy-coding-standard.yml
services:
ObjectCalisthenics\Sniffs\NamingConventions\ElementNameMinimalLengthSniff: ~
Configurable
7. Keep Your Classes Small
class SimpleStartupController
{
// 300 lines of code
}
class SimpleStartupController
{
// 50 lines of code
}
class SomeClass
{
public function simpleLogic()
{
// 30 lines of code
}
}
class SomeClass
{
public function simpleLogic()
{
// 10 lines of code
}
}
class SomeClass
{
// 20 properties
}
class SomeClass
{
// 5 properties
}
class SomeClass
{
// 20 methods
}
class SomeClass
{
// 5 methods
}
Use Just These Ones?
--sniffs=ObjectCalisthenics.Files.ClassTraitAndInterfaceLength,ObjectCalisthenics.Files.FunctionLengthSniff,ObjectCalisthenics.Metrics.MethodPerClassLimit,ObjectCalisthenics.Metrics.PropertyPerClassLimitSniff
# easy-coding-standard.yml
services:
ObjectCalisthenics\Sniffs\Files\ClassTraitAndInterfaceLengthSniff: ~
ObjectCalisthenics\Sniffs\Files\FunctionLengthSniff: ~
ObjectCalisthenics\Sniffs\Metrics\MethodPerClassLimitSniff: ~
ObjectCalisthenics\Sniffs\Metrics\PropertyPerClassLimitSniff: ~
Configurable
9. Do not Use Getters and Setters
This rules is partially related to Domain Driven Design.
- Classes should not contain public properties.
- Method should represent behavior, not set values.
class ImmutableBankAccount
{
public $currency = 'USD';
private $amount;
public function setAmount(int $amount)
{
$this->amount = $amount;
}
}
class ImmutableBankAccount
{
private $currency = 'USD';
private $amount;
public function withdrawAmount(int $withdrawnAmount)
{
$this->amount -= $withdrawnAmount;
}
}
bmitch/Codor
Codor.ControlStructures.NoElse
Does not allow for any else
or elseif
statements.
if ($foo) {
return 'bar';
} else {
return 'baz';
}
if ($foo) {
return 'bar';
}
return 'baz';
Codor.Files.FunctionLength
Functions/methods must be no more than 20 lines.
public function foo()
{
// more than 20 lines
}
public function foo()
{
// no more than 20 lines
}
Codor.Files.FunctionParameter
Functions/methods must have no more than 3 parameters.
public function foo($bar, $baz, $bop, $portugal)
{
//
}
public function foo($bar, $baz, $bop)
{
//
}
Codor.Files.ReturnNull
Functions/methods must not return null
.
public function getAdapter($bar)
{
if ($bar === 'baz') {
return new BazAdapter;
}
return null;
}
public function getAdapter($bar)
{
if ($bar === 'baz') {
return new BazAdapter;
}
return NullAdapter;
}
Codor.Files.MethodFlagParameter
Functions/methods cannot have parameters that default to a boolean.
public function getCustomers($active = true)
{
if ($active) {
// Code to get customers from who are active
}
// Code to get all customers
}
public function getAllCustomers()
{
// Code to get all customers
}
public function getAllActiveCustomers()
{
// Code to get customers from who are active
}
Codor.Classes.ClassLength
Classes must be no more than 200 lines.
class ClassTooLong
{
// More than 200 lines
}
class ClassNotTooLong
{
// No more than 200 lines
}
Codor.Classes.ConstructorLoop
Class constructors must not contain any loops.
public function __construct()
{
for($index = 1; $index < 100; $index++) {
// foo
}
}
public function __construct()
{
$this->someMethod();
}
private function someMethod()
{
for($index = 1; $index < 100; $index++) {
// foo
}
}
Codor.Classes.Extends
Warns if a class extends another class. Goal is to promote composition over inheritance (https://en.wikipedia.org/wiki/Composition_over_inheritance).
class GasolineCar extends Vehicle
{
//
}
class GasolineVehicle extends Vehicle
{
//
}
class Vehicle
{
private $fuel;
public function __construct(FuelInterface $fuel)
{
$this->fuel;
}
}
class Gasoline implements FuelInterface
{
}
$gasolineCar = new Vehicle($gasoline);
Codor.Classes.FinalPrivate
Final classes should not contain protected methods or variables. Should use private instead.
final class Foo
{
protected $baz;
protected function bar()
{
//
}
}
final class Foo
{
private $baz;
private function bar()
{
//
}
}
Codor.Classes.PropertyDeclaration
Produces an error if your class uses undeclared member variables. Only warns if class extends another class.
class Foo
{
private function bar()
{
$this->baz = 13;
}
}
class Foo
{
private $baz;
private function bar()
{
$this->baz = 13;
}
}
Codor.Files.FunctionNameContainsAndOr
Functions/methods cannot contain "And" or "Or". This could be a sign of a function that does more than one thing.
public function validateStringAndUpdateDatabase()
{
// code to validate string
// code to update database
}
public function validateString()
{
// code to validate string
}
public function updateDatabase()
{
// code to update database
}
Codor.Files.IndentationLevel
Functions/methods cannot have more than 2 level of indentation.
public function foo($collection)
{
foreach ($collection as $bar) {
foreach ($bar as $baz) {
//
}
}
}
public function foo($collection)
{
foreach ($collection as $bar) {
$this->process($bar);
}
}
private function process($bar)
{
foreach ($bar as $baz) {
//
}
}
Codor.ControlStructures.NestedIf
Nested if statements are not allowed.
public function allowedToDrink($person)
{
if ($person->age === 19) {
if ($person->hasValidId()) {
return true;
}
}
return false;
}
public function allowedToDrink($person)
{
if ($person->age !== 19) {
return false;
}
if (! $person->hasValidId()) {
return false;
}
return true;
}
Codor.Syntax.NullCoalescing
Produces an error if a line contains a ternary operator that could be converted to a Null Coalescing operator.
$username = isset($customer['name']) ? $customer['name'] : 'nobody';
$username = $customer['name'] ?? 'nobody';
Codor.Syntax.LinesAfterMethod
Only allows for 1 line between functions/methods. Any more than 1 will produce an error.
public function foo()
{
//
}
public function bar()
{
//
}
public function foo()
{
//
}
public function bar()
{
//
}
Codor.TypeHints.MixedReturnType
Prevents you from having a mixed
type returned in a doc block.
/**
* @return mixed
*/
public function foo()
{
//
}
/**
* @return string
*/
public function foo()
{
//
}