Object calisthenics (in PHP)
Tags: php, calisthenic, softwaredevelopment, codequality
Published: 2023-08-23 08:01:01

## Table of Contents
&nbsp;[1. Only one level of indentation per method](#1-only-one-level-of-indentation-per-method)
&nbsp;[2. Do not use else](#2-do-not-use-raw-else-endraw-)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[2.1. Early return](#early-return)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;[2.2. Return parametrized](#return-parametrized)
&nbsp;[3. Wrap all primitives and string](#3-wrap-all-primitives-and-raw-string-endraw-)
&nbsp;[4. First class collections](#4-first-class-collections)
&nbsp;[5. One dot per line](#5-one-dot-per-line)
&nbsp;[6. Don't abbreviate](#6-dont-abbreviate)
&nbsp;[7. Keep all entities small](#7-keep-all-entities-small)
&nbsp;[8. No classes with more than two instance variables](#8-no-classes-with-more-than-two-instance-variables)
&nbsp;[9. No getters, setters, properties](#9-no-getters-setters-properties)
&nbsp;[Summary](#summary)

In classical Greek, calisthenics (καλός-σθένος) etymologically means beautiful of strength. In general, calisthenics were "physical exercises usually done repeatedly to keep your muscles in good condition and improve the way you look or feel"[^1].

Despite its explicit reference to physical activity, the concept of "object calisthenics" exists in software engineering. What does it mean?

Object calisthenics' aims to improve code. Concretely, to improve the maintainability, readability, testability and comprehensibility of your code. By repeating a set of 9 exercises, you will naturally change how you code and you will improve it.

As the definition says, object calisthenics consists of 9 exercises to apply when you code whenever possible, not a dogma you must follow no matter what.

## 1. Only one level of indentation per method

This rule establish that there should only be one indentation level for each of the methods we have within a class.

Suppose we have the following class:

```php
final readonly class ProcessVideoAudioElements
{
    public function execute(array $videoElements, array $audioElements): void
    {
        foreach($videoElements as $videoElement) {
            foreach ($audioElements as $audioElement) {
                $this->api->call($videoElement, $audioElement);
            }
        }
    }
}
```

Applying this rule, we could refactor the code as follows:

```php
final readonly class ProcessVideoAudioElements
{
    public function execute(array $videos, array $audios): void
    {
         $this->executeApiCalls($videos, $audios);
    }
    
    public function executeApiCalls(array $videos, array $audios): void
    {
        foreach ($videos as $video) {
            $this->handleVideo($video, $audios);
        }
    }
    
    public function handleVideo(Video $video, array $audios): void
    {
        foreach($audios as $audio) {
            $this->api->call($video, $audio);
        }
    }
}
```

## 2. Do not use `else`

This rule proposes not using the `else` keyword in our conditions.

We have two possible ways to apply this rule.

### 2.1. Early return

Having the following method:

```php
public function login(string $user, string $password): Response
{
    if ($this->repo->isValid($user, $password)) {
        return new Response('ok', 200);
    } else {
        return new Response('error', 400);
    }
}
```

we could apply the rule and refactor the code as follows:

```php
public function login(string $user, string $password): Response
{
    if ($this->repo->isValid($user, $password)) {
        return new Response('ok', 200);
    }
    
    return new Response('error', 400);
}
```

### 2.2. Return parametrized

With the following code:

```php
public function link(string $status): string
{
    if ($status === 'subscribed') {
        $link = 'enabled';
    } else {
        $link = 'not-enabled';
    }
    
    return $link;
}
```

we could refactor it to:

```php
public function link(string $status): string
{
    $link = 'not-enabled';

    if ($status === 'subscribed') {
        $link = 'enabled';
    }
    
    return $link;
}
```

## 3. Wrap all primitives and `string`

This rule proposes to use Value Objects to encapsulate all primitives within objects that have their own meaning.

For example, we could have the following class:

```php
final readonly class Order
{
    private string $id;
    
    private int $numLines;
    
    private float $totalAmount;
}
```

Applying the rule, we could have a Value Object for each of the primitives:

```php
final readonly class Order
{
    private Id $id;
    
    private Quantity $numLines;
    
    private Money $totalAmount;
}
```

## 4. First class collections

This rule proposes to wrap all `array` constructions within their own class.

Imagine we have this class:

```php
final readonly class Order
{
    /** Line[] */
    private array $lines;
    
    private function calculateTotalAmount(): void
    {
        foreach($this->lines as $line) {
            // Line may actually be anything!
        }
    }
}
```

`$this->lines` could be anything, even with a PHPDoc comment.

Thus, we could have a `LineCollection` that contains all those elements:

```php
final class LineCollection
{
    /** @var Line[] */
    private array $elements = [];
    private int $count = 0;
    
    public function add(Line $line): void
    {
        $this->elements[] = $line;
        $this->count++;
    }
}
```

We would then refactor the main class to:

```php
final readonly class Order
{
    private LineCollection $lines;
    
    private function calculateTotalAmount(): void
    {
        foreach($this->lines as $line) {
            // Line can only be of type Line!
        }
    }
}
```

## 5. One dot per line

This rule is also known as Demeter's Law: don't talk to strangers, because it breaks code encapsulation.

Suppose we have the following classes:

```php
final readonly class Language
{
    private Code $code;
    
    public function code(): Code
    {
        return $this->code;
    }
}

final readonly class Audio
{
    private Language $language;
    
    public function language(): Language
    {
        return $this->language;
    }
}

final readonly class Response
{
    private Audio $audio;
    
    public function audio(): Audio
    {
        return $this->audio;
    }
}
```

We could then have the following chain of calls:

```php
echo $response->audio()->language()->code();
```

However, what would happen if some of the methods changed its return type, in order to accept `null`, for example? Code will break, as we are breaking code encapsulation.

Applying this rule, we could refactor `Audio` and `Response` classes:

```php
final readonly class Audio
{
    private Language $language;
    
    public function languageCode(): Code
    {
        return $this->language->code();
    }
}

final readonly class Response
{
    private Audio $audio;
    
    public function audioLanguageCode(): Code
    {
        return $this->audio->languageCode();
    }
}
```

We could then have the following call, that does not break code encapsulation:

```php
echo $response->audioLanguageCode();
```

## 6. Don't abbreviate

This rule talks on its own.

Suppose we have the following class:

```php
final readonly class AudioStrResCol
{
    // ...
}
```

What does `AudioStrResCol` mean?
- `AudioStringResourceColumn`?
- `AudioStringResultColumn`?
- `AudioStringResultCollation`?
- `AudioStrongResultColumn`?

Actually, the class name should be:

```php
final readonly class AudioStreamResponseCollection
{
    // ...
}
```

## 7. Keep all entities small

Files with lots of lines of code are hard to read. Thus, this rule proposes to keep all classes between 50 and 150 lines,

Nonetheless, this rules is not always easy to follow, as it depends on the programming language you use.

## 8. No classes with more than two instance variables

This rule, as the previous one, is not always easy to follow.

Suppose we have the following class:

```php
final class Customer
{
    private Id $id;
    
    private Email $email;
    private PasswordHash $passwordHash;
    
    private FirstName $firstName;
    private LastName $lastName;
    
    private Name $address;
    private City $city;
    private PostalCode $postalCode;
}
```

Applying the rule, we could refactor the code to be:

```php
final class Customer
{
    private Id $id;
    private UserData $userData;
}

final class UserData
{
    private AuthData $authData;
    private PersonalData $personalData;
}

final class AuthData
{
    private Email $email;
    private PasswordHash $passwordHash;
}

final class PersonalData
{
    private NameData $nameData;
    private AddressData $addressData;
}

final class NameData
{
    private FirstName $firstName;
    private LastName $lastName;
}

final class AddressData
{
    private Name $address;
    private CityData $cityData;
}

final class CityData
{
    private City $city;
    private PostalCode $postalCode;
}
```

## 9. No _getters_, _setters_, _properties_

This rule is also known as "tell, don't ask". It consists on not asking (get) before performing an action, but to encapsulate the behavior within the object.

For example, if we have the following code:

```php
if ($cart->getAmount() >= 100) {
    $cart->setDiscount(20);
}
```

We could refactor for it to be:

```php
final class Cart
{
    private const DISCOUNT_THRESHOLD = 100;
    private const DISCOUNT_AMOUNT = 20;

    public function applyDiscount(): void
    {
        if ($this->totalAmount < self::DISCOUNT_THRESHOLD) {
            return; // Throw exception
        }
        
        $this->discount = self::DISCOUNT_AMOUNT;
    }
}
```

Then, it could be called with:

```php
$cart->applyDiscount();
```

## Summary

Object calisthenics consists of 9 exercises/rules to apply when writing code to improve the maintainability, readability, testability and comprehensibility of your code.

Not all rules are always applicable, but it is important to keep them in mind as a guide to follow.

[^1]: [Cambridge dictionary](https://dictionary.cambridge.org/dictionary/english/calisthenics)