# PHP style guide

PSR-12 (opens new window) serves as the base guideline to be followed. Our guidelines overrule or supplement the PSR.

# Class defaults

Use private as the default visibility modifier for class methods, properties and constants.

Mark classes as final by default.

final class UserResource extends Resources
{
    //
}

# Strict typing

Add strict type declarations to every file like this:

<?php declare(strict_types = 1);

Why?

See Using type hints without strict_types may lead to subtle bugs (opens new window)

Starting every new file with <?php declare(strict_types = 1); serves as a good habit. It can be automated by using PhpStorm file templates. Even when it has no advantage to the code currently present in the file, it might affect future additions.

# 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.

# Return types on closures

Always add return types to closures.

// Yes
return once(function (): VideoCollection {

    return Video::query()
        ->where('foo', 'bar')
        ->get();
});

// No
return once(function () {

    return Video::query()
        ->where('foo', 'bar')
        ->get();
});

# Named arguments

From the RFC (opens new window):

Named arguments allow passing arguments to a function based on the parameter name, rather than the parameter position. This makes the meaning of the argument self-documenting, makes the arguments order-independent, and allows skipping default values arbitrarily.

Only use named arguments in case that saves us from providing arguments that are already defaulted. Or when the meaning of a magic number or boolean cannot be determined.

# Spaces

For a better readability, add a space after unary Not (!).

// Yes
if (! $foo) {
    // do work
}

// No
if (!$foo) {
    // do work
}

# 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.

// Yes
class Url
{
    public static function fromString(string $url): self
    {
        // ...
    }
}

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

In the following example the $notifiable parameter can't be type hinted, because the method signature should be compatible with the parent class. In this case the method description and @param tag provide additional information, a @return tag would not. Therefore, only the latter should be omitted.

// Yes
class ResetPasswordNotification extends Notification
{
    /**
      * Build the mail representation of the notification.
      *
      * @param User $notifiable
      */
    public function toMail($notifiable): MailMessage
    {
        // ...
    }
}

Don't use fully qualified class names in docblocks.

// Yes

use Spatie\Url\Url;

/**
 * @param string $foo
 *
 * @return Url
 */

// No

/**
 * @param string $url
 *
 * @return \Spatie\Url\Url
 */

Docblocks for class properties are required as long as they are not typed (available in PHP 7.4).

// Yes

class Foo
{
    /**
     * @var Url 
     */
    protected $url;

    /**
     * @var string 
     */
    protected $name;
}

// No

class Foo
{
    protected $url;
    protected $name;
}

When possible, docblocks should be written on one line. Docblocks for methods and properties should be written multiline.

// Yes

/** @var ModelFactory $factory */
$factory->define(User::class, []);

// No

/**
 * @var ModelFactory $factory
 */
$factory->define(User::class, []);
// Yes

/**
 * @mixin Collection
 */
class CollectionMacros
{
    //
}

// No

/** @mixin Collection */
class CollectionMacros
{
    //
}

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

// Yes

/** @var Url|null */

// No

/** @var null|Url */

# Strings

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

// Yes
$greeting = "Hi, I am {$name}.";
// No
$greeting = 'Hi, I am ' . $name . '.';

# Ternary operators

In general, only use a ternary expression when this results in a very short, single line, readable expression.

// Yes
$name = $isFoo ? 'foo' : 'bar';

// When an early return can be used, this is preferred...
if (! $object instanceof Model) {

    return $object->name;
}

return 'A default value';

// ... over this:
$result = $object instanceof Model
    ? $object->name
    : 'A default value';

# If statements

# Bracket position

Always use curly brackets.

// Yes
if ($condition) {
   ...
}

// No
if ($condition) ...

# Empty line after conditional

This is a loose guideline, as it may depend on what you think will provide the best readability.

// Yes: both the conditional expression and its condition are short
if ($condition) {
   return;
}

// Yes: separate a larger conditional expression from its condition
if ($condition) {

    return [
        'foo' => 'bar',
        'x' => 'y',
    ];
}

# 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.

// Yes

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

// do work
// No

if ($goodCondition) {
    // do work
}

throw new Exception;

# 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.

// Yes

if (! $conditionBA) {
   // do work

   return;
}

if (! $conditionB) {
   // do work

   return;
}

// do work
// No

if ($conditionA) {
   if ($conditionB) {
        // do work
   }
   else {
        // do work     
   }
}
else {
    // do work     
}

# Compound ifs

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

// Yes
if ($a !== 'foo') {
   return;
}

if (! $b instanceof Bar) {
   return;
}

if ($c >= $x) {
   return;
}

// do work

// Yes: compound condition for a short, readable expression
if ($conditionA && $b->isFoo() && $c->is_bar) {
    // do work
}

// No
if ($a !== 'foo' && ! $b instanceof Bar && $c >= $x) {
    // do work
}

# 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.
 */

# Whitespace

Statements should have to breathe. In general always add blank lines between statements, unless they're a sequence of single-line equivalent operations. This isn't something enforceable, it's a matter of what looks best in its context.

// Yes
public function getPage($url): ? Page
{
    $page = $this->pages()
        ->where('slug', $url)
        ->first();

    if (! $page) {
        return null;
    }

    if ($page['private'] && ! Auth::check()) {
        return null;
    }

    return $page;
}

// No: Lines are cramped together.
public function getPage($url): ? Page
{
    $page = $this->pages()->where('slug', $url)->first();
    if (! $page) {
        return null;
    }
    if ($page['private'] && ! Auth::check()) {
        return null;
    }
    return $page;
}
// Yes: A sequence of single-line equivalent operations.
public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->increments('id');
        $table->string('name');
        $table->string('email')->unique();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}

An empty line after an opening curly bracket ({) is optional and depends on what you think serves the best readability. Don't add any empty line before the closing curly bracket (}) however.

// Yes
if ($foo) {
    # this empty line is optional
    $this->foo = $foo;
}

// No
if ($foo) {
    $this->foo = $foo;

}

# Methods

Function calls and declarations should have a trailing comma, if the parameters or arguments are distributed over multiple lines. Applying this leads to a cleaner diff in git.

// Constructor property promotion
public function __construct(
    private string $foo,
) {
    //
}

// Regular method
private function hasValidIphoneHash(): bool
{
    return IphoneController::canViewOnIphone(
        $this->video()->uuid,
        $this->get('hihaho-iphone'),
        $this->get('time'),
    );
}