Laravel & PHP Style Guide

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.

General PHP Rules

Code style must follow PSR-1 and PSR-2.

.editorConfig

Please reference this repository for our standard .editorconfig configuration.

Variable Naming

  • Choose clear, descriptive names.
  • Generally speaking, everything string-like that's not public-facing should use camelCase.
  • Do not abbreviate variable names as the gains added by shortening typically adds a layer of obscurity to what the variable contains.
// Good
$userName = 'jschutt';
$password = 'BigFoot123';
$lastName = 'Schutt';
$query = 'SELECT * FROM users;'

// Bad
$uName = 'jschutt';
$pw = 'BigFoot123';
$last_name = 'Schutt'; // Prefer camelCase wherever possible
$q = 'SELECT * FROM users;'

Docblocks

Because docblocks aid in IDE Intellisense, implement them.

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 Url
{
    /** 
     * @param string $url
     * 
     * @return \Zaengle\Url\Url
     */
    public static function fromString(string $url): Url
    {
        // ...
    }
}

// Bad: The description is redundant.
class Url
{
    /**
     * Create a url from a string.
     * 
     * @param string $url
     * 
     * @return \Zaengle\Url\Url
     */
    public static function fromString(string $url): Url
    {
        // ...
    }
}

Shorten docblocks by importing resources.

// Good
use \Zaengle\Workflow\Generator;

class Demo {
    /**
     * @param string $title
     *
     * @return Generator
     */
}

// Bad
class Demo {
    /**
     * @param string $title
     *
     * @return \Zaengle\Workflow\Generator
     */
}

Ternary operators

Every portion of a ternary expression should be on it's own line unless it's a really short expression.

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

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

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

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 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 enforcable, it's a matter of what looks best in it's 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;

}

Precede a return with a single empty line.

public function show() {
	$users = User::all();
	
	return view('users.index', compact('users');
}

Do not include more than 1 empty line for spacing purposes.

// bad
public function show() {
	...
}



public function edit() {
	...
}

Configuration

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.

Artisan commands

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!');
}

If possible use a descriptive success message eg. Old records deleted.

Routing

Public-facing urls must use kebab-case, and be plural when referring to a resource.

https://zaengle.com/open-source
https://zaengle.com/jobs/front-end-developer

Route names will ideally consist of single words, separated by dots, but must use kebab_case if multiple words are required. Include the default index, show, edit, create, etc as the last segment of a named route. Route name segments should be plural.

Route::get('open-source', 'OpenSourceController@index')->name('open-source.index');
<a href="{{ route('open-source.index') }}">
    Open Source
</a>

Attempt to use route groups when possible.

// bad: elements are unnecessarily repeated
Route::get('users', 'UserController@index')->name('users.index');
Route::get('users/{user}', 'UserController@show')->name('users.show');

// good: shared elements are in the route group
Route::group([
    'as'     => 'users.',
    'prefix' => 'users'
], function () {
    Route::get('/', 'UserController@index')->name('index');
    Route::get('{user}', 'UserController@show')->name('show');
});

All routes have an http verb, that's why we like to put the verb first when defining a route. It makes a group of routes very readble. Any other route options should come after it.

// good: all http verbs come first
Route::get('/', 'HomeController@index')->name('home');
Route::get('open-source', 'OpenSourceController@index')->middleware('openSource');

// bad: http verbs not easily scannable
Route::name('home')->get('/', 'HomeController@index');
Route::middleware('openSource')->get('OpenSourceController@index');

Controllers

Controllers that control a resource must use the singular resource name.

class PostController
{
    // ...
}

Try to keep controllers simple and stick to the default CRUD keywords (index, create, store, show, edit, update, destroy). Extract a new controller if you need other actions.

In the following example, we could have PostController@favorite, and PostController@unfavorite, or we could extract it to a seperate FavoritePostController.

class PostController
{
    public function create()
    {
        // ...
    }
    
    // ...
    
    public function favorite(Post $post)
    {
        request()->user()->favorites()->attach($post);
        
        return response(null, 200);
    }

    public function unfavorite(Post $post)
    {
        request()->user()->favorites()->detach($post);
        
        return response(null, 200);
    }
}

Here we fall back to default CRUD words, create and destroy.

class FavoritePostController
{
    public function create(Post $post)
    {
        request()->user()->favorites()->attach($post);
        
        return response(null, 200);
    }

    public function destroy(Post $post)
    {
        request()->user()->favorites()->detach($post);
        
        return response(null, 200);
    }
}

Views

View files must use kebab_case and be nested within plural named folders of it's parent resource.

resources/
  views/
    users/
      show.blade.php
      index.blade.php
// visible at 'site.com/users'
class UserController
{
    public function index() {
        return view('users.index');
    }
}

Validation

All custom validation rules must use snake_case:

Validator::extend('organisation_type', function ($attribute, $value) {
    return OrganisationType::isValid($value);
});

Blade Templates

Indent using four spaces.

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

Don't add spaces after control structures.

@if($condition)
    Something
@endif

Authorization

Policies must use camelCase.

Gate::define('editPost', function ($user, $post) {
    return $user->id == $post->user_id;
});
@can('editPost', $post)
    <a href="{{ route('posts.edit', $post) }}">
        Edit
    </a>
@endcan

Try to name abilities using default CRUD words. One exception: replace show with view. A server shows a resource, a user views it.