Appearance
PHP style guide
PSR-12 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.
php
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
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.
php
// ✅
return once(function (): VideoCollection {
return Video::query()
->where('foo', 'bar')
->get();
});
// ❌
return once(function () {
return Video::query()
->where('foo', 'bar')
->get();
});Named arguments
From the RFC:
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 (!).
php
// ✅
if (! $foo) {
// do work
}
// ❌
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.
php
// ✅
class Url
{
public static function fromString(string $url): self
{
// ...
}
}
// ❌ 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.
php
// ✅
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.
php
// ✅
use Spatie\Url\Url;
/**
* @param string $foo
*
* @return Url
*/
// ❌
/**
* @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).
php
// ✅
class Foo
{
/**
* @var Url
*/
protected $url;
/**
* @var string
*/
protected $name;
}
// ❌
class Foo
{
protected $url;
protected $name;
}When possible, docblocks should be written on one line. Docblocks for methods and properties should be written multiline.
php
// ✅
/** @var ModelFactory $factory */
$factory->define(User::class, []);
// ❌
/**
* @var ModelFactory $factory
*/
$factory->define(User::class, []);php
// ✅
/**
* @mixin Collection
*/
class CollectionMacros
{
//
}
// ❌
/** @mixin Collection */
class CollectionMacros
{
//
}If a variable has multiple types, the most common occurring type should be first.
php
// ✅
/** @var Url|null */
// ❌
/** @var null|Url */Strings
When possible prefer string interpolation above sprintf and the . operator.
php
// ✅
$greeting = "Hi, I am {$name}.";php
// ❌
$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.
php
// ✅
$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.
php
// ✅
if ($condition) {
...
}
// ❌
if ($condition) ...Empty line after conditional
This is a loose guideline, as it may depend on what you think will provide the best readability.
php
// ✅ both the conditional expression and its condition are short
if ($condition) {
return;
}
// ✅ 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.
php
// ✅
if (! $goodCondition) {
throw new Exception;
}
// do workphp
// ❌
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.
php
// ✅
if (! $conditionBA) {
// do work
return;
}
if (! $conditionB) {
// do work
return;
}
// do workphp
// ❌
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.
php
// ✅
if ($a !== 'foo') {
return;
}
if (! $b instanceof Bar) {
return;
}
if ($c >= $x) {
return;
}
// do work
// ✅ compound condition for a short, readable expression
if ($conditionA && $b->isFoo() && $c->is_bar) {
// do work
}php
// ❌
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:
php
// 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.
php
// ✅
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;
}
// ❌ 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;
}php
// ✅ 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.
php
// ✅
if ($foo) {
# this empty line is optional
$this->foo = $foo;
}
// ❌
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.
php
// 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'),
);
}