PHP Object Calisthenics Made Simple - Version 3.0 is Out Now

Object Calisthenics are 9 language-agnostic rules to help you write better and cleaner code. They help you to get rid of "else" statements, method chaining, long classes or functions, unreadable short names and much more.

Object Calisthenics 3.0 runs on CodeSniffer 3.0 and PHP 7.1. It brings 6 of them with fancy configuration and code examples.

If you are a coding standard nerd like me, you'll probably have more than just PSR-2 standard in your ruleset. But even if you don't, Object Calisthenics is a developer-friendly game changer for your code.

Much Simpler than 2.0

1. You don't have to know a single thing about CodeSniffer to start

2. Simple to install

composer require object-calisthenics/phpcs-calisthenics-rules

3. You can start with 1 sniff

Quick quiz: what is this variable?

$this->di->...;

Dependency Injection? Dependency Injection Container? Who would guess it's Donation Invoice!

Rule #6 - Do Not Abbreviate checks these cases. It detects short names that are ambiguous and hard to decode.

To run it from the command line just include Object Calisthenics' ruleset.xml and specify the sniff's name:

vendor/bin/phpcs src -sp \
--standard=vendor/object-calisthenics/phpcs-calisthenics-rules/src/ObjectCalisthenics/ruleset.xml \
--sniffs=ObjectCalisthenics.NamingConventions.ElementNameMinimalLength

You can run this locally or put to your CI and you are ready to go.

Nothing complicated.

Fancy Readme With Examples

We put lots of work to README for the new release. It isn't a long text describing what exactly the rule does and how it originated - there is already a blog post for that.

Instead, README goes right to the point:

Configure What You Need

As you can see in the bottom part of screenshot, most of rules are configurable. It allows you to adapt their strictness to your specific needs and needs of your project.

Do you prefer to require min 4 chars?

Configure in CodeSniffer:

<!-- ruleset.xml -->

<?xml version="1.0"?>
<ruleset name="ObjectCalisthenics">
    <!-- Rule 6: Do not abbreviate -->
    <rule ref="ObjectCalisthenics.NamingConventions.ElementNameMinimalLength">
        <properties>
            <property name="minLength" value="4"/> <!-- default: 3 -->
        </properties>
    </rule>
</ruleset>

Configure in EasyCodingStandard:

# easy-coding-standard.neon

checkers:
    # Rule 6: Do not abbreviate
    ObjectCalisthenics\Sniffs\NamingConventions\ElementNameMinimalLengthSniff:
        minLength: 4 # default: 3

Do you want to add "y" to allowed short names?

Configure in CodeSniffer:

<!-- ruleset.xml -->

<?xml version="1.0"?>
<ruleset name="ObjectCalisthenics">
    <!-- Rule 6: Do not abbreviate -->
    <rule ref="ObjectCalisthenics.NamingConventions.ElementNameMinimalLength">
        <properties>
            <property name="minLength" value="4"/> 
            <property name="allowedShortNames" type="array" value="y,i,id,to,up"/>
            <!-- default: i,id,to,up -->
        </properties>
    </rule>
</ruleset>

Configure in EasyCodingStandard:

# easy-coding-standard.neon

checkers:
    # Rule 6: Do not abbreviate
    ObjectCalisthenics\Sniffs\NamingConventions\ElementNameMinimalLengthSniff:
        minLength: 4
        allowedShortNames: ["y", "i", "id", "to", "up"] 
        # default: ["i", "id", "to", "up"]

Minitip: What Can You Configure in Particular Sniff?

That was Rule 6.

5 more 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($sniffGefroup, Sniff::class);
}

// ...
private function ensureIsAllInstanceOf(array $objects, string $type)
{
    // ...
}

2. Do Not Use "else" Keyword

if ($status === self::DONE) {
    $this->finish();
} else {
    $this->advance();
}

if ($status === self::DONE) {
    $this->finish();
    return;
}

$this->advance();

5. Use Only One Object Operator (->) per Line

$this->container->getBuilder()->addDefinition(SniffRunner::class);

$containerBuilder = $this->getContainerBuilder();
$containerBuilder->addDefinition(SniffRunner::class);

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
}

9. Do not Use Getters and Setters

Classes should not contain public properties.

class ImmutableBankAccount
{
    public $currency = 'USD';

class ImmutableBankAccount
{
    private $currency = 'USD';

Method should represent behavior, not set values.

    private $amount;

    public function setAmount(int $amount)
    {
        $this->amount = $amount;
    }
}

    private $amount;

    public function withdrawAmount(int $withdrawnAmount)
    {
        $this->amount -= $withdrawnAmount;
    }
}

Check the README to see how to use them and configure them.

9 – 6 = 3... Where is the Rest of Rules?

There are 3 more rules to complete the list from the original manifesto:

  1. Wrap Primitive Types and Strings
  2. Use First Class Collections
  3. Do Not Use Classes With More Than Two Instance Variables

They are mostly related to DDD (Domain Driven Design), too strict to use in practise or too vague to cover them with semantic rule.

Thanks to all Contributors

Last but not least, I'd like to personally thank contributors who helped to make this version happen as it is:

Without your help, this would not have been possible. Thank you guys.

To Sum up Object Calisthenics 3.0

To find more details about this release see Release notes on Github.



What do you think?