Home

Awesome

Code Odor Sniffer

:nose: :poop: Custom PHP Code Sniffer sniffs to help find Code Smells (Odor).

Build Status codecov Scrutinizer Code Quality Code Climate Packagist Packagist License

Inspired by: https://github.com/object-calisthenics/phpcs-calisthenics-rules

What is it?

This package is a set of custom Sniffs for the PHP Code Sniffer that you can use in your CI build to ensure the ingegrity of your code base.

Compatibility

How to Install?

Install via Composer:

composer require bmitch/codor --dev

How to Use?

Create a PHPCS ruleset XML (codor.xml or whatever filename you want) file in the root of your project.

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Project">
    <description>Project Coding Standard</description>

    <rule ref="vendor/bmitch/codor/src/Codor/ruleset.xml"/>
</ruleset>

Then run it with the command:

vendor/bin/phpcs --standard=codor.xml src 

Where src is the location of the source code you want to check.

Omitting Sniffs

You may not want to run all the sniffs provided so you can specify which sniffs you want to exclude with the --exclude flag like:

vendor/bin/phpcs --standard=codor.xml --exclude=Codor.ControlStructures.NoElse src

(if you want to exclude multiple just separate them with a comma)

Running Specific Sniffs

Or you can also specify which sniffs to specifically run:

vendor/bin/phpcs --standard=codor.xml --sniffs=Codor.ControlStructures.NoElse src

Suppressing the sniffs on specific pieces of code

Please see the PHPCS documentation:
https://github.com/squizlabs/PHP_CodeSniffer/wiki/Advanced-Usage#ignoring-files-and-folders

Sniffs Included

Codor.ControlStructures.NoElse

Does not allow for any else or elseif statements.

:x:

if ($foo) {
    return 'bar';
} else {
    return 'baz';
}

:white_check_mark:

if ($foo) {
    return 'bar';
}
return 'baz';

Codor.Files.FunctionLength

Functions/methods must be no more than 20 lines.

:x:

public function foo()
{
    // more than 20 lines
}

:white_check_mark:

public function foo()
{
    // no more than 20 lines
}

Codor.Files.FunctionParameter

Functions/methods must have no more than 3 parameters.

:x:

public function foo($bar, $baz, $bop, $portugal)
{
    // 
}

:white_check_mark:

public function foo($bar, $baz, $bop)
{
    // 
}

Codor.Files.ReturnNull

Functions/methods must not return null.

:x:

public function getAdapter($bar)
{
    if ($bar === 'baz') {
        return new BazAdapter;
    }
    return null;
}

:white_check_mark:

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.

:x:

public function getCustomers($active = true)
{
    if ($active) {
        // Code to get customers from who are active
    }
    
    // Code to get all customers
}

:white_check_mark:

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.

:x:

class ClassTooLong
{
    // More than 200 lines
}

:white_check_mark:

class ClassNotTooLong
{
    // No more than 200 lines
}

Codor.Classes.ConstructorLoop

Class constructors must not contain any loops.

:x:

public function __construct()
{
    for($index = 1; $index < 100; $index++) {
        // foo
    }
}

:white_check_mark:

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).

:x:

class GasolineCar extends Vehicle
{
    //
}

class GasolineVehicle extends Vehicle
{
    //
}

:white_check_mark:

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.

:x:

final class Foo 
{
    protected $baz;

    protected function bar()
    {
        //
    }
}

:white_check_mark:

final class Foo 
{
    private $baz;

    private function bar()
    {
        //
    }
}

Codor.Classes.NewInstance

Classes should not instantiate objects. Should use dependency injection.

:x:

class NewInConstructor
{
    private MyClass $myClass;

    public function __construct()
    {
        $this->myClass = new MyClass();
    }
}

:white_check_mark:

class NewInConstructor
{
    private MyClass $myClass;

    public function __construct(MyClass $myClass)
    {
        $this->myClass = $myClass;
    }
}

Codor.Classes.PropertyDeclaration

Produces an error if your class uses undeclared member variables. Only warns if class extends another class.

:x:

class Foo 
{
    private function bar()
    {
        $this->baz = 13;
    }
}

:white_check_mark:

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.

:x:

public function validateStringAndUpdateDatabase()
{
    // code to validate string
    // code to update database
}

:white_check_mark:

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.

:x:

public function foo($collection)
{
    foreach ($collection as $bar) {
        foreach ($bar as $baz) {
            //
        }
    }
}

:white_check_mark:

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.

:x:

public function allowedToDrink($person)
{
    if ($person->age === 19) {
        if ($person->hasValidId()) {
            return true;
        }
    }
    
    return false;
}

:white_check_mark:

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.

:x:

$username = isset($customer['name']) ? $customer['name'] : 'nobody';

:white_check_mark:

$username = $customer['name'] ?? 'nobody';

Codor.Syntax.LinesAfterMethod

Only allows for 1 line between functions/methods. Any more than 1 will produce an error.

:x:

public function foo()
{
    //
}


public function bar()
{
    //
}

:white_check_mark:

public function foo()
{
    //
}

public function bar()
{
    //
}

Codor.TypeHints.MixedReturnType

Prevents you from having a mixed type returned in a doc block.

:x:

/**
 * @return mixed
 */
public function foo()
{
    //
}

:white_check_mark:

/**
 * @return string
 */
public function foo()
{
    //
}

Customizing Sniffs

Some of the sniff rules can be customized to your liking. For example, if you'd want the Codor.Files.FunctionLength to make sure your functions are no more than 30 lines instead of 20, you can do that. Here's an example of a codor.xml file with that customization:

<?xml version="1.0" encoding="UTF-8"?>
<ruleset name="Project">
    <description>Project Coding Standard</description>

    <rule ref="vendor/bmitch/codor/src/Codor/ruleset.xml"/>
	<rule ref="Codor.Files.FunctionLength">
		<properties>
			<property name="maxLength" value="30"/>
		</properties>
	</rule>
</ruleset>

Customizations Available

Similar Packages

Contributing

Please see CONTRIBUTING.md

License

The MIT License (MIT). Please see License File for more information.