Skip the setup. Our team will have you taking orders in 24 hours. Get started →
Resources

PHP Coding Guidelines

General PHP Rules Naming Convention Jobs Events Listeners Shorter and readable syntax More examples: Docblocks Comments Whitespace...

Consistency is key

Coding Standards are critical for achieving high code quality. We can produce a homogeneous code that is easy to read and maintain by using a consistent visual style, naming conventions, and other technical settings.

Code style must follow PSR-1, PSR-2 and PSR-12. Generally speaking, everything string-like (except Model attributes) should use camelCase. Detailed examples on these are spread throughout the guide in their relevant sections.

At TastyIgniter, we don't use final by default.

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

Good:

public ?string $variable;

Bad:

public string|null $variable;

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

// in a Laravel model
public function scopeArchived(Builder $query): void
{
    $query->
    ...
}

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

class Foo
{
    public string $bar;
}
class Foo
{
    /** @var string */
    public $bar;
}

Values in enums should use PascalCase.

enum Suit {  
    case Clubs;
    case Diamonds;
    case Hearts;
    case Spades;
}

Suit::Diamonds;

Naming things is often seen as one of the harder things in programming. That's why we've established some high level guidelines for naming classes.

Also, follow naming conventions accepted by Laravel community:

What How Good Bad
Controller plural ArticlesController ArticleController
Route plural articles/1 article/1
Model singular User Users
Table plural article_comments article_comment, articleComments
Pivot table singular model names article_user articles_users
Table column snake_case without model name meta_title MetaTitle, article_meta_title
Foreign key singular model name with _id suffix article_id ArticleId, id_article, articles_id
Primary key - id custom_id
Migration - 2017_01_01_000000_create_articles_table 2017_01_01_000000_articles
Method camelCase getAll get_all
Function snake_case abort_if abortIf
Method in test class camelCase testGuestCannotSeeArticle test_guest_cannot_see_article
Model property snake_case $model->model_property $model->modelProperty
Variable camelCase $anyOtherVariable $any_other_variable
Collection descriptive, plural $activeUsers = User::active()->get() $active, $data
Object descriptive, singular $activeUser = User::active()->first() $users, $obj
Config and language files index snake_case articles_enabled ArticlesEnabled, articles-enabled
View file name kebab-case show-filtered.blade.php showFiltered.blade.php, show_filtered.blade.php
Config file name kebab-case google-calendar.php googleCalendar.php, google_calendar.php
Contract (interface) adjective or noun Authenticatable AuthenticationInterface, IAuthentication
Trait adjective Notifiable NotificationTrait

A job's name should describe its action and an optional Job suffix.

Example: CreateUser or PerformDatabaseCleanupJob

Events will often be fired before or after the actual event. This should be very clear by the tense used in their name.

Example: ApprovingCustomer before the action is completed and CustomerApproved after the action is completed.

Listeners will perform an action based on an incoming event. Their name should reflect that action.

Example: SendInvitationMailListener

Use shorter and more readable syntax where possible.

Good:

session('cart');
$request->name;

Bad:

$request->session()->get('cart');
$request->input('name');

Consider using helpers instead of facades. They can clean up your code.

Common syntax Shorter and more readable syntax
Session::get('foo') session('foo')
$request->session()->get('foo') session('foo')
Session::put('foo', $data) session(['foo' => $data])
$request->input('name'),Request::get('name') $request->name,request('name')
return Redirect::back() return redirect()->back()
is_null($object->relation) ? $object->relation->id : null; optional($object->relation)->id
return view('index')->with('title', $title)->with('client', $client) return view('index', compact('title', 'client'))
$request->has('value') ? $request->value : 'default'; $request->get('value','default')
Carbon::now(), Carbon::today() now(), today()
App::make('Class') app('Class')
->where('column', '=', 1) ->where('column', 1)
->orderBy('created_at', 'desc') ->latest()
->orderBy('age', 'desc') ->latest('age')
->orderBy('created_at', 'asc') ->oldest()
->select('id', 'name')->get() ->get(['id', 'name'])
->first()->name ->value('name')

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.

Good:

class Foo
{
    public static function fromString(string $bar): Foo
    {
        // ...
    }
}

Bad:

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

Always use fully qualified class names in docblocks.

Good:

/**
 * @param string $url
 *
 * @return \Igniter\Flame\Foo
 */

Bad:

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

Using multiple lines for a docblock, might draw too much attention to it. When possible, docblocks should be written on one line.

Good:

/** @var string */
/** @test */

Bad:

/** * @test */

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

Good:

/** @var \Igniter\Flame\Foo|null */

Bad:

/** @var null|\Igniter\Flame\Foo */

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

Good:

$this->calculateTotals();

Bad:

// Start calculating totals

Comment your code, but prefer descriptive method and variable names over comments

Good:

if ($this->hasJoins())

Bad:

if (count((array) $builder->getQuery()->joins) > 0)

Statements should be allowed 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.

Good:

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

    if (!$page)
        return null;

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

    return $page;
}

Bad:

// Everything's cramped together.
public function getPage($url)
{
    $page = $this->pages()->where('slug', $url)->first();
    if (!$page)
        return null;
    if ($page['private'] && !Auth::check())
        return null;
    return $page;
}

Good:

// 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();
    });
}

Don't add any extra empty lines between {} brackets.

Good:

if ($foo) {
    $this->foo = $foo;
}

Bad:

if ($foo) {

    $this->foo = $foo;

}

A class and a method should have only one responsibility.

Good:

public function getFullNameAttribute()
{
    return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}

public function isVerifiedClient()
{
    return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}

public function getFullNameLong()
{
    return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}

public function getFullNameShort()
{
    return $this->first_name[0] . '. ' . $this->last_name;
}

Bad:

public function getFullNameAttribute()
{
    if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
        return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
    } else {
        return $this->first_name[0] . '. ' . $this->last_name;
    }
}

Put all DB related logic into Eloquent models if you're using Query Builder or raw SQL queries.

Good:

public function index()
{
    return view('index', ['clients' => $this->client->getWithNewOrders()]);
}

class Client extends Model
{
    public function getWithNewOrders()
    {
        return $this->verified()
            ->with(['orders' => function ($q) {
                $q->where('created_at', '>', Carbon::today()->subWeek());
            }])
            ->get();
    }
}

Bad:

public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}

For facades, always use the fully qualified class name

Good:

use Illuminate\Support\Facades\Config;

Bad:

use Config;

Each applied trait should go on its own line, and the use keyword should be used for each of them. This will result in clean diffs when traits are added or removed.

Good:

class MyClass
{
    use TraitA;
    use TraitB;
}

Bad:

class MyClass
{
    use TraitA, TraitB;
}

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

Good:

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

Bad:

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

Always use curly brackets, except it is a one line if condition

Good:

if ($condition) {
   $this->doSomething();
   $this->doSomethingElse();
}

if ($conidition)
   $this->doSomething();

Bad:

if ($condition) 
   $this->doSomething()
   $this->doSomethingElse();

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.

Good:

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

// do work

Bad:

if ($goodCondition) {
 // do work
}

throw new Exception;

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.

Good:

if (!$conditionA) {
   // condition A failed
   return;
}

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

// condition A and B passed

Bad:

if ($conditionA) {
   if ($conditionB) {
      // condition A and B passed
   }
   else {
     // condition A passed, B failed
   }
}
else {
   // condition A failed
}

Another option to refactor an else away is using a ternary

Good:

$condition
    ? $this->doSomething()
    : $this->doSomethingElse();

Bad:

if ($condition) {
    $this->doSomething();
}
else {
    $this->doSomethingElse();
}

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

Good:

if ($conditionA)
   return;

if (!$conditionB)
   return;

if (!$conditionC)
   return;

// do stuff

Bad:

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

Configuration files must use kebab-case.

config/
pdf-generator.php

Configuration keys must use snake_case.

// config/pdf-generator.php
return [
    'chrome_path' => env('CHROME_PATH'),
];

Avoid using the env helper outside of configuration files. Create a configuration value from the env variable like above.

Good:

// config/api.php
'key' => env('API_KEY'),

// Use the data
$apiKey = config('api.key');

Bad:

$apiKey = env('API_KEY');

Use language strings instead of text in the code

Good:

return redirect()->back()->with('message', lang('app.menu_added'));

Bad:

return redirect()->back()->with('message', 'Your menu has been added!');

The names given to artisan commands should all be kebab-cased.

Good:

php artisan delete-old-records

Bad:

php artisan deleteOldRecords

A command should always give some feedback on what the result is. Minimally you should let the handle method spit out a comment at the end indicating that all went well.

// in a Command
public function handle()
{
    // do some work

    $this->comment('All ok!');
}

When the main function of a result is processing items, consider adding output inside the loop, so progress can be tracked. Put the output before the actual process. If something goes wrong, this makes it easy to know which item caused the error.

At the end of the command, provide a summary on how much processing was done.

// in a Command
public function handle()
{
    $this->comment("Start processing items...");

    // do some work
    $items->each(function(Item $item) {
        $this->info("Processing item id `{$item-id}`...");
        $this->processItem($item);
    });

    $this->comment("Processed {$item->count()} items.");
}

Indent using four spaces.

<a href="/open-source">
    Open Source
</a>

Don't add spaces after control structures.

@if($condition)
    Something
@endif

Minimize usage of vanilla PHP in Blade templates.

When using multiple rules for one field in a form request, avoid using |, always use array notation. Using an array notation will make it easier to apply custom rule classes to a field.

Good:

public function rules()
{
    return [
        'body' => 'required',
        'publish_at' => ['nullable', 'date'],
    ];
}

Bad:

public function rules()
{
    return [
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ];
}

Move validation from controllers to FormRequest classes.

Good:

class Post extends FormRequest
{
    public function rules()
    {
        return [
            'body' => 'required',
            'publish_at' => ['nullable', 'date'],
        ];
    }
}

Bad:

$request->validate([
    'body' => 'required',
    'publish_at' => ['nullable', 'date'],
]);

Eloquent is preferred over Query Builder and raw SQL queries. Eloquent allows you to write code that is both readable and maintainable. Eloquent has great built-in tools like soft deletes, events, scopes etc.

Good:

Article::has('user.profile')->verified()->latest()->get();

Bad:

SELECT *
    FROM `articles`WHERE EXISTS (SELECT *
                  FROM `users`WHERE `articles`.`user_id` = `users`.`id`AND EXISTS (SELECT *
                              FROM `profiles`WHERE `profiles`.`user_id` = `users`.`id`)
                  AND `users`.`deleted_at` IS NULL)
    AND `verified` = '1'AND `active` = '1'ORDER BY `created_at` DESC

When collections can help you clean up your code, use them.

Good:

$collection = collect([
    ['name' => 'Regena', 'age' => null],
    ['name' => 'Linda', 'age' => 14],
    ['name' => 'Diego', 'age' => 23],
    ['name' => 'Linda', 'age' => 84],
]);

$collection->firstWhere('name', 'Linda');

Bad:

$array = [
    ['name' => 'Regena', 'age' => null],
    ['name' => 'Linda', 'age' => 14],
    ['name' => 'Diego', 'age' => 23],
    ['name' => 'Linda', 'age' => 84],
];

$person = null;
foreach ($array as $value) {
    if ($value['name'] === 'Linda') {
        $person = $value;
    }
}

Eager loading helps in solving the N+1 query problem.

Good:

// for 100 users, 2 DB queries will be executed
$users = User::with('profile')->get();

Bad:

// for 100 users, 101 DB queries will be executed
$users = User::all()

The above are sections taken from the Spatie PHP Code Guidelines.

Did this answer your question?

Your feedback helps us improve our help articles.

See a mistake? Edit this page on GitHub

Need more help?

Explore the help center, join the community, or get priority support from the TastyIgniter team.