# General PHP Rules

This page contains general PHP coding guidelines for AirLST project.

Guidelines are inspired by Spatie's Laravel and PHP guidelines (opens new window) and using it as a base.

# About Laravel

First and foremost, Laravel provides the most value when you write things the way Laravel intended you to write. If there's a documented way to achieve something, follow it. Whenever you do something differently, make sure you have a justification for why you didn't follow the defaults.

# Nullable and union types

Whenever possible use the short nullable notation of a type, instead of using a union of the type with null.

// Bad
public string | null $name;

// Good
public ?string $name;

# Void return types

If a method returns nothing, it should be indicated with void. This makes it more clear to the users of your code what your intention was when writing it.

class PublishPostAction
{
    public function execute(Post $post): void
    {
        $post->published = true;
        $post->save();
    }
}

# Typed properties

You should type a property whenever possible. Don't use a docblock.

// good
class Post
{
    // Bad
    /** @var string */
    public $name;
    
    // Good
    public string $name;
}

# Docblocks

Don't use docblocks for methods that can be fully type hinted (unless you need a description).

Only add a description when it provides more context than the method signature itself. Use full sentences for descriptions, including a period at the end.

class Url
{
    // Bad: The description is redundant, and the method is fully type-hinted.
    /**
     * Create a url from a string.
     *
     * @param string $url
     *
     * @return App\Service\Url
     */
    public static function fromString(string $url): Url
    {
        // ...
    }
    
    // Good
    public static function fromString(string $url): Url
    {
        // ...
    }
}

Always use fully qualified class names in docblocks.

// Bad
/**
 * @param string $url
 *
 * @return Url
 */

// Good
/**
 * @param string $url
 *
 * @return App\Service\Url
 */

If a variable has multiple types, the most common occurring type should be first.

// Bad
/** @var null|\App\Service\Url */

// Good
/** @var \App\Service\Url|null */

# Constructor property promotion

Use constructor property promotion if all properties can be promoted. If there's more than one argument, put each one on a line of its own.

// Bad
class MyClass {
    protected string $secondArgument

    public function __construct(protected string $firstArgument, string $secondArgument)
    {
        $this->secondArgument = $secondArgument;
    }
}

// Good
class MyClass {
    public function __construct(
        protected string $firstArgument,
        protected string $secondArgument,
    ) {}
}

# Strings

When possible prefer string interpolation above sprintf and the . operator.

// Bad
$greeting = 'Hi, I am ' . $name . '.';

// Good
$greeting = "Hi, I am {$name}.";

# if statements

# Bracket position

Always use curly brackets.

// Bad
if ($condition) run_something();

// Good
if ($condition) {
    run_something();
}

# Happy path

Generally a function should have its unhappy path first and its happy path last. In most cases this will cause the happy path being in an unindented part of the function which makes it more readable.

// Bad
if ($goodCondition) {
    // do work
}

throw new Exception;
// Good
if (! $goodCondition) {
    throw new Exception;
}

// do work

# Avoid else

In general, else should be avoided because it makes code less readable. In most cases it can be refactored using early returns. This will also cause the happy path to go last, which is desirable.

// Bad
if ($conditionA) {
    if ($conditionB) {
        // condition A and B passed
    }
    else {
        // condition A passed, B failed
    }
}
else {
    // condition A failed
}
// Good
if (! $conditionA) {
    // condition A failed
    
    return;
}

if (! $conditionB) {
    // condition A passed, B failed
    
    return;
}

// condition A and B passed

Another option to refactor an else away is using a ternary

// Bad
if ($condition) {
    $this->doSomething();
} 
else {
    $this->doSomethingElse();
}
// Good
$condition ? $this->doSomething(); : $this->doSomethingElse();

# Compound ifs

In general, separate if statements should be preferred over a compound condition. This makes debugging code easier.

// Bad
if ($conditionA && $conditionB && $conditionC) {
    // do stuff
}

// Good
if (! $conditionA) {
    return;
}

if (! $conditionB) {
    return;
}

if (! $conditionC) {
    return;
}

// do stuff

# switch statements

Avoid using switch statements, they look clunky, generally occupy more lines of code.

Refactor switch statements if conditions are not more than two. But even better, use match expression instead of switch.

// Bad
switch ($i) {
    case 1:
        echo "i equals 1";
        break;
    case 2:
        echo "i equals 2";
        break;
}

// Good
if ($i == 1) {
    return 'i equals 1';
}
if ($i == 2) {
    return 'i equals 2';
}

// Better
$result = match($i) {
    1 => 'i equals 1',
    2 => 'i equals 2',
};

# Comments

Comments should be avoided as much as possible by writing expressive code. If you do need to use a comment, format it like this:

// There should be a space before a single line comment.

/*
 * If you need to explain a lot you can use a comment block. Notice the
 * single * on the first line. Comment blocks don't need to be three
 * lines long or three characters shorter than the previous line.
 */

A possible strategy to refactor away a comment is to create a function with name that describes the comment

// Bad
// Start calculating loans

// Good
$this->calculateLoans();