# 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();