bmitch/Codor

View on GitHub
README.md

Summary

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

[![Build Status](https://github.com/bmitch/Codor/workflows/Continuous%20Integration/badge.svg)](https://github.com/bmitch/Codor/actions)
[![codecov](https://codecov.io/gh/bmitch/Codor/branch/master/graph/badge.svg)](https://codecov.io/gh/bmitch/Codor) 
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/bmitch/Codor/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/bmitch/Codor/?branch=master) 
[![Code Climate](https://codeclimate.com/github/bmitch/Codor/badges/gpa.svg)](https://codeclimate.com/github/bmitch/Codor) 
[![Packagist](https://img.shields.io/packagist/v/bmitch/codor.svg)](https://packagist.org/packages/bmitch/codor) 
[![Packagist](https://img.shields.io/packagist/dt/bmitch/codor.svg)](https://packagist.org/packages/bmitch/codor/stats)
[![License](https://img.shields.io/packagist/l/bmitch/codor.svg)](LICENSE.md)
----------
_Inspired by: https://github.com/object-calisthenics/phpcs-calisthenics-rules_

* [What Is it?](#what-is-it)
* [Compatibility](#compatibility)
* [How to Install?](#how-to-install)
* [How to Use?](#how-to-use)
 * [Omitting Sniffs](#omitting-sniffs)
 * [Running Specific Sniffs](#running-specific-sniffs)
* [Sniffs Included](#sniffs-included)
* [Customizing Sniffs](#customizing-sniffs)
 * [Customizations Available](#customizations-available)
* [Similar Packages](#similar-packages)
* [Contributing](#contributing)
* [License](#license)

## What is it? ##
This package is a set of custom Sniffs for the [PHP Code Sniffer](https://github.com/squizlabs/PHP_CodeSniffer) that you can use in your CI build to ensure the ingegrity of your code base.

## Compatibility ##
* PHP 7.1+ please use v1.0.0 and above.
* PHP 5.6 and below please use any version below v1.0.0.

## 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
<?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:
```php
if ($foo) {
    return 'bar';
} else {
    return 'baz';
}
```

:white_check_mark:
```php
if ($foo) {
    return 'bar';
}
return 'baz';
```

### Codor.Files.FunctionLength ###
Functions/methods must be no more than 20 lines.

:x:
```php
public function foo()
{
    // more than 20 lines
}
```

:white_check_mark:
```php
public function foo()
{
    // no more than 20 lines
}
```
### Codor.Files.FunctionParameter ###
Functions/methods must have no more than 3 parameters.

:x:
```php
public function foo($bar, $baz, $bop, $portugal)
{
    // 
}
```

:white_check_mark:
```php
public function foo($bar, $baz, $bop)
{
    // 
}
```
### Codor.Files.ReturnNull ###
Functions/methods must not return `null`.

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

:white_check_mark:
```php
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:
```php
public function getCustomers($active = true)
{
    if ($active) {
        // Code to get customers from who are active
    }
    
    // Code to get all customers
}
```

:white_check_mark:
```php
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:
```php
class ClassTooLong
{
    // More than 200 lines
}
```

:white_check_mark:
```php
class ClassNotTooLong
{
    // No more than 200 lines
}
```
### Codor.Classes.ConstructorLoop ###
Class constructors must not contain any loops.

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

:white_check_mark:
```php
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:
```php
class GasolineCar extends Vehicle
{
    //
}

class GasolineVehicle extends Vehicle
{
    //
}
```

:white_check_mark:
```php
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:
```php
final class Foo 
{
    protected $baz;

    protected function bar()
    {
        //
    }
}
```

:white_check_mark:
```php
final class Foo 
{
    private $baz;

    private function bar()
    {
        //
    }
}
```

### Codor.Classes.NewInstance ###
Classes should not instantiate objects. Should use dependency injection.

:x:
```php
class NewInConstructor
{
    private MyClass $myClass;

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

:white_check_mark:
```php
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:
```php
class Foo 
{
    private function bar()
    {
        $this->baz = 13;
    }
}
```

:white_check_mark:
```php
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:
```php
public function validateStringAndUpdateDatabase()
{
    // code to validate string
    // code to update database
}
```

:white_check_mark:
```php
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:
```php
public function foo($collection)
{
    foreach ($collection as $bar) {
        foreach ($bar as $baz) {
            //
        }
    }
}
```

:white_check_mark:
```php
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:
```php
public function allowedToDrink($person)
{
    if ($person->age === 19) {
        if ($person->hasValidId()) {
            return true;
        }
    }
    
    return false;
}
```

:white_check_mark:
```php
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:
```php
$username = isset($customer['name']) ? $customer['name'] : 'nobody';
```

:white_check_mark:
```php
$username = $customer['name'] ?? 'nobody';
```

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

:x:
```php
public function foo()
{
    //
}


public function bar()
{
    //
}
```

:white_check_mark:
```php
public function foo()
{
    //
}

public function bar()
{
    //
}
```

### Codor.TypeHints.MixedReturnType ###
Prevents you from having a `mixed` type returned in a doc block.

:x:
```php
/**
 * @return mixed
 */
public function foo()
{
    //
}
```

:white_check_mark:
```php
/**
 * @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
<?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
* `Codor.Files.FunctionLength`
 * `maxLength`: The maximum number of lines a function/method can have (default = 20).
* `Codor.Files.FunctionParameter`
 * `maxParameters`: The maximum number of parameters a function/method can have (default = 3).
* `Codor.Classes.ClassLength`
 * `maxLength`: The maximum number of lines a Class can have (default = 200).
* `Codor.Files.IndentationLevel`
 * `indentationLimit`: Cannot have more than or equal to this number of indentations (default = 2).

## Similar Packages
* https://github.com/object-calisthenics/phpcs-calisthenics-rules
* https://github.com/slevomat/coding-standard

## Contributing ##
Please see [CONTRIBUTING.md](CONTRIBUTING.md)

## License ##

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.