Laravel Best Coding Practice

What is methodology?

It means project development planning. Naming convention (terminology) is also a part of it.
Terminology (camelCase vs PascalCase and snake_case)
  1. Pascal case is a subset of Camel Case where the first letter is capitalized. Where Pascal case = Upper camel case and Camel case = Lower camel case.
  2. That is, userAccount is a camel case and UserAccount is a Pascal case.
  3. The conventions of using these are different. You use camel case for variables and Pascal case for Class names or Constructors.
  4. It is easy to remember. Pascal is a proper noun so capitalize the first letter. camel is a common noun, so you do not capitalize the first letter.
  5. In camel case spaces, numbers, underscores, hyphens, and other special characters are removed and only string is allowed.
  6. Snake case rules are:
    • All letters are lowercase.
    • All spaces between words are filled with underscores.
    • Remove all punctuation.
    • Ex. I’m writing code”  turns into “im_writing_code”

Laravel naming conventions

Remember that “conventions” are just conventions and you could do whatever you want just be constant, however it’s better follow the documentation:
  1. Table name: plural and _ to separate words (users, tags, …)
  2. Columns name: singular and _ to separate words (user, tag, …)
  3. Models: singular with first letter capitalized and capitalization to separate words (User, Tag, …)
  4. Controllers: singular with capitalized first letter and capitalization to separate words followed by “Controller” (UserController, TagController, …)
  5. Migrations: plural and _ to separate words (create_articles_table >> 2018_10_17_233629_create_articles_table)
Must visit for all the references and good practices: https://github.com/alexeymezenin/laravel-best-practices#follow-laravel-naming-conventions

Details:


How to read this guide
  • MUST This word, or the terms “REQUIRED” or “SHALL”, mean that the definition is an absolute requirement of the specification.
  • MUST NOT This phrase, or the phrase “SHALL NOT”, mean that the definition is an absolute prohibition of the specification.
  • SHOULD This word, or the adjective “RECOMMENDED”, mean that there may exist valid reasons in particular circumstances to ignore a particular item, but the full implications must be understood and carefully weighed before choosing a different course.
  • SHOULD NOT This phrase, or the phrase “NOT RECOMMENDED” mean that there may exist valid reasons in particular circumstances when the particular behavior is acceptable or even useful, but the full implications should be understood and the case carefully weighed before implementing any behavior described with this label.
  • MAY this word, or the adjective “OPTIONAL”, mean that an item is truly optional.
The above specifications is derived from RFC 2119
Environment
Local Environment
There are multiple ways to run Laravel in local environment as outlined below:
You are free to choose which local environment that works for you and your team as long as the following conditions are met:
  • Your local environment MUST be able to duplicate the production server or close to its specifications such as PHP version, extensions installed and MySQL version
·         .env file MUST NOT be committed into repository
  • You SHOULD NOT be connecting to your any production server when you are debugging locally, to prevent accidental corruption of data, unintended API call or similar incident.
  • You SHOULD NOT be using personally identiable information (PII) of your end-users data or any data that could potentially identify a specific individual such as first/last name, address, medical condition so on and so forth, unless you are explicitly authorized by your company or client to do so.
  • You MUST update the readme.md file for any special instruction on how to run the app in local environment, so that other developers who will setup the app in their local machine can follow them properly.
  • While it is possible to WAMP or XAMP for Laravel, this is un-common practice so you SHOULD try to familiarize yourself on how server components works and be comfortable in dealing with them.
Staging Environment
Staging servers is a type of server that is used to test a software, website or service in a production-similar environment before being set live. It is the role of a staging environment or staging site, to serve as a temporary hosting and testing server for any new software or feature.
Production Environment
You MUST regularly rotate your APP_KEY
APP_KEYS are set when you initialized a new Laravel application or executed the following command
php artisan key:generate
Laravel uses the key for all encrypted cookies, including the session cookie, before handing them off to the user’s browser, and it uses it to decrypt cookies read from the browser. This prevents the client from making changes to their cookies and granting themselves admin privileges or impersonating another user in your application. Encrypted cookies are an important security feature in Laravel
Configuration
Environment Variables
You MUST put sensitive information into .env files
Use .env files to store any secure information and retrieve it via env function. There should be no instance on which you will put it inside models/controllers and commit it to Git.
Good
// .env

API_HOST=https://example.com/api
API_USERNAME=myuser
API_PASSWORD=secret


// access the value from app/config.php file

return [
    ...
    'api_host' => env('API_HOST', 'https://defaultdomain.com')
    'api_username' => env('API_USER', 'defaultuser')
    'api_password' => env('API_USER', 'defaultpassword')
    ...
]

Bad
define('API_HOST', 'https://defaultdomain.com');
define('API_USERNAME', 'defaultuser');
define('API_PASSWORD', 'defaultpassword');

class DomainController extends Controller
{
    public function index()
    {
      $api_username  
    }

Your application key MUST be set. This is the APP_KEY variable in your .env file. You can generate one via
php artisan key:generate
Application Namespace
It is recommended to give your application a name. That is, instead of using the default App root namespace given by Laravel install, set it to what the application is all about. This can be set via
php artisan app:name YourAppName
This makes your controllers/models etc. resolve into YourAppName\Controllers and YourAppName\Models
Package Configuration
Custom or Package configuration filename MUST be in snake_case
Good
config/my_config.php
Bad
config/MyConfig.php
Config and language files indexes SHOULD be in snake-case
Good
// config/myconfig.php
return [
    'my_api' => [
        'domain' => env('API_DOMAIN'),
        'secret' => env('API_SECRET'),
    ],
Bad
// config/myconfig.php
return [
    'MyApi' => [
        'DOMAIN' => env('API_DOMAIN'),
        'SECRET' => env('API_SECRET'),
    ],
The best way to figure out if you have implemented best-practice in configuring your app, is if the codebase could be made open source at any moment without compromising any credentials
Functions
Laravel comes with a lot of useful helper functions, but you can also define your own helper functions, given the following conditions:
You SHOULD place your custom helper functions by creating a file called helper.php
Good
project_folder/app/helper.php
project_folder/app/Http/helper.php
Bad
project_folder/functions.php
You MUST use Composer’s autolading capability to load your functions
Good
// file composer.json
...
"autoload": {
    "files": [
        "app/helpers.php"
    ],
...
Bad
// file app/Http/Controllers/HomeController.php

class HomeController.php
{
    function index(){
        require_once(app_path("helpers.php"));
    }
}

You MUST check if the the function exists before defining it
Good
if (! function_exists('my_custom_helper')) {
    function my_custom_helper($key, $default = null) {
        // ...
    }
}
Bad
function my_custom_helper($key, $default = null) {
    // ...
}
Other General guides with functions
  • If the function length exceeds 25 lines, you SHOULD break it down to multiple functions
  • Each function SHOULD have a Unit Test associated with it
Views
You SHOULD use snake_case as file name of your Blade templates
Good
show_filtered.blade.php
Bad
showFiltered.blade.php
show-filtered.blade.php
You MUST not make non UI-related operations inside blade templates
Good

// $api_results is passed by controller
<ul> 
    @foreach($api_results as $result)
        <li>{{ $result->name }}</li>
    @endforeach
</ul>

Bad

@php
   $api_results = json_decode(file_get_contents("https://api.example.com"));
@endphp
<ul>
    @foreach($api_results as $result)
        <li>{{ $result->name }}</li>
    @endforeach
</ul>

Database Conventions
Table and Fields Naming
Table names MUST be in plural form and MUST be all lower-case
Good
class CreateFlightsTable extends Migration
{
    public function up()
    {
        Schema::create('flights', function (Blueprint $table) {
Bad
class CreateFlightsTable extends Migration
{
    public function up()
    {
        Schema::create('flight', function (Blueprint $table) {
class CreateUsersTable extends Migration
{
    public function up()
    {
        Schema::create('MyUsers', function (Blueprint $table) {
Pivot table names MUST be in singular model names in alphabetical order
Good
post_user
articles_user
photo_post
Bad
posts_users
user_articles
post_photos
Table column names SHOULD be in snake_case without the model name
Good
username
title
thumb_url
Bad
UserName
_title
ThumbUrl
post_title
Foreign keys MUST be singular model name with _id suffix
Good
user_id
Bad
userid
siteid
Memberid
TransactionID
Primary Keys SHOULD be “id”
Good
id
Bad
ID
pkid
guid
Database Alterations
You MUST not be changing the database schema directly, use Database Migrations instead
Good
php artisan migrate
Bad
  • use of PHPMyAdmin
  • directly executing ALTER statement in mysql console / cli
  • using sql file to change the db
Migration filenames MUST follow to following pattern
creation of table
yyyy_mm_dd_<timestamp>_create_<table name>_table
Good
2019_06_06_164210_create_domains_table.php
Bad
2019_06_06_164210_domains.php
Database Choice
Polyglot Persistence
Is a practice of using different data storage technologies for different kinds of data. Eloquent ORM can support multiple database for a reason, so don’t limit yourself to MySQL.
  • It is RECOMMENDED to use MongoDB for records that have attributes that vary a lot. For example, in an inventory system, an office supplies product might have a different set of fields compared to vehicle and auto supplies.
  • It is RECOMMENDED to use ElasticSearch for high volume data searching and indexing.
  • It is RECOMMENDED to use Neo4J for applications that require complex relationships between models. For example a multi-level networking application, social network site and similar apps.
From this article, here is a sample breakdown of different databases being used by a retailer company
Design Patterns
SOLID
SOLID is five design principles intended to make software designs more understandable, flexible and maintainable.
Single responsibility principle
A class should only have a single responsibility, that is, only changes to one part of the software’s specification should be able to affect the specification of the class.
Open–closed principle
Software entities … should be open for extension, but closed for modification.
Liskov substitution principle
Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program.
Interface segregation principle
Many client-specific interfaces are better than one general-purpose interface.
Dependency inversion principle
One should depend upon abstractions, [not] concretions.
Repository Pattern
The idea with this pattern is to have a generic abstract way for the app to work with the data layer without being bothered what storage technology is used when saving/retrieving the data.
We suggests to check first this <a href=https://medium.com/@jsdecena/refactor-the-simple-tdd-in-laravel-a92dd48f2cdd>tutorial</a> for in-depth understand about this design pattern.
When reading/writing data, it is RECOMMENDED to wrap it into Repository Object
Testing
Unit Testing
Methods in test classes MUST start with “test” then a camelCased name of the test
Good
class ExampleTest extends TestCase
{
    public function testBasicTest()
    {
Bad
class ExampleTest extends TestCase
{
    public function test_basic_test()
    {

Single responsibility principle
A class and a method should have only one responsibility.
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;
    }
}
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;
}
Fat models, skinny controllers
Put all DB related logic into Eloquent models or into Repository classes if you're using Query Builder or raw SQL queries.
Bad:
public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();

    return view('index', ['clients' => $clients]);
}
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();
    }
}
Validation
Move validation from controllers to Request classes.
Bad:
public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);

    ....
}
Good:
public function store(PostRequest $request)
{   
    ....
}

class PostRequest extends Request
{
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
            'publish_at' => 'nullable|date',
        ];
    }
}
Business logic should be in service class
A controller must have only one responsibility, so move business logic from controllers to service classes.
Bad:
public function store(Request $request)
{
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images') . 'temp');
    }
   
    ....
}
Good:
public function store(Request $request)
{
    $this->articleService->handleUploadedImage($request->file('image'));

    ....
}

class ArticleService
{
    public function handleUploadedImage($image)
    {
        if (!is_null($image)) {
            $image->move(public_path('images') . 'temp');
        }
    }
}
Don't repeat yourself (DRY)
Reuse code when you can. SRP is helping you to avoid duplication. Also, reuse Blade templates, use Eloquent scopes etc.
Bad:
public function getActive()
{
    return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->where('verified', 1)->whereNotNull('deleted_at');
        })->get();
}
Good:
public function scopeActive($q)
{
    return $q->where('verified', 1)->whereNotNull('deleted_at');
}

public function getActive()
{
    return $this->active()->get();
}

public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->active();
        })->get();
}
Prefer to use Eloquent over using Query Builder and raw SQL queries. Prefer collections over arrays
Eloquent allows you to write readable and maintainable code. Also, Eloquent has great built-in tools like soft deletes, events, scopes etc.
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
Good:
Article::has('user.profile')->verified()->latest()->get();
Mass assignment
Bad:
$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Add category to article
$article->category_id = $category->id;
$article->save();
Good:
$category->article()->create($request->validated());
Do not execute queries in Blade templates and use eager loading (N + 1 problem)
Bad (for 100 users, 101 DB queries will be executed):
@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach
Good (for 100 users, 2 DB queries will be executed):
$users = User::with('profile')->get();

...

@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach
Comment your code, but prefer descriptive method and variable names over comments
Bad:
if (count((array) $builder->getQuery()->joins) > 0)
Better:
// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
Good:
if ($this->hasJoins())
Do not put JS and CSS in Blade templates and do not put any HTML in PHP classes
Bad:
let article = `{{ json_encode($article) }}`;
Better:
<input id="article" type="hidden" value='@json($article)'>

Or

<button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
In a Javascript file:
let article = $('#article').val();
The best way is to use specialized PHP to JS package to transfer the data.
Use config and language files, constants instead of text in the code
Bad:
public function isNormal()
{
    return $article->type === 'normal';
}

return back()->with('message', 'Your article has been added!');
Good:
public function isNormal()
{
    return $article->type === Article::TYPE_NORMAL;
}

return back()->with('message', __('app.article_added'));
Use standard Laravel tools accepted by community
Prefer to use built-in Laravel functionality and community packages instead of using 3rd party packages and tools. Any developer who will work with your app in the future will need to learn new tools. Also, chances to get help from the Laravel community are significantly lower when you're using a 3rd party package or tool. Do not make your client pay for that.
Task
Standard tools
3rd party tools
Authorization
Policies
Entrust, Sentinel and other packages
Compiling assets
Laravel Mix
Grunt, Gulp, 3rd party packages
Development Environment
Homestead
Docker
Deployment
Laravel Forge
Deployer and other solutions
Unit testing
PHPUnit, Mockery
Phpspec
Browser testing
Laravel Dusk
Codeception
DB
Eloquent
SQL, Doctrine
Templates
Blade
Twig
Working with data
Laravel collections
Arrays
Form validation
Request classes
3rd party packages, validation in controller
Authentication
Built-in
3rd party packages, your own solution
API authentication
Laravel Passport
3rd party JWT and OAuth packages
Creating API
Built-in
Dingo API and similar packages
Working with DB structure
Migrations
Working with DB structure directly
Localization
Built-in
3rd party packages
Realtime user interfaces
Laravel Echo, Pusher
3rd party packages and working with WebSockets directly
Generating testing data
Seeder classes, Model Factories, Faker
Creating testing data manually
Task scheduling
Laravel Task Scheduler
Scripts and 3rd party packages
DB
MySQL, PostgreSQL, SQLite, SQL Server
MongoDB
Follow Laravel naming conventions
Also, follow naming conventions accepted by Laravel community:
What
How
Good
Bad
Controller
singular
ArticleController
ArticlesController
Route
plural
articles/1
article/1
Named route
snake_case with dot notation
users.show_active
users.show-active, show-active-users
Model
singular
User
Users
hasOne or belongsTo relationship
singular
articleComment
articleComments, article_comment
All other relationships
plural
articleComments
articleComment, article_comments
Table
plural
article_comments
article_comment, articleComments
Pivot table
singular model names in alphabetical order
article_user
user_article, articles_users
Table column
snake_case without model name
meta_title
MetaTitle; article_meta_title
Model property
snake_case
$model->created_at
$model->createdAt
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
Method in resource controller
store
saveArticle
Method in test class
camelCase
testGuestCannotSeeArticle
test_guest_cannot_see_article
Variable
camelCase
$articlesWithAuthor
$articles_with_author
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
kebab-case
show-filtered.blade.php
showFiltered.blade.php, show_filtered.blade.php
Config
snake_case
google_calendar.php
googleCalendar.php, google-calendar.php
Contract (interface)
adjective or noun
Authenticatable
AuthenticationInterface, IAuthentication
Trait
adjective
Notifiable
NotificationTrait
Use shorter and more readable syntax where possible
Bad:
$request->session()->get('cart');
$request->input('name');
Good:
session('cart');
$request->name;
More examples:
Common syntax
Shorter and more readable syntax
Session::get('cart')
session('cart')
$request->session()->get('cart')
session('cart')
Session::put('cart', $data)
session(['cart' => $data])
$request->input('name'), Request::get('name')
$request->name, request('name')
return Redirect::back()
return back()
is_null($object->relation) ? null : $object->relation->id
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')
Use IoC container or facades instead of new Class
new Class syntax creates tight coupling between classes and complicates testing. Use IoC container or facades instead.
Bad:
$user = new User;
$user->create($request->validated());
Good:
public function __construct(User $user)
{
    $this->user = $user;
}

....

$this->user->create($request->validated());
Do not get data from the .env file directly
Pass the data to config files instead and then use the config() helper function to use the data in an application.
Bad:
$apiKey = env('API_KEY');
Good:
// config/api.php
'key' => env('API_KEY'),

// Use the data
$apiKey = config('api.key');
Store dates in the standard format. Use accessors and mutators to modify date format
Bad:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
Good:
// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at'];
public function getSomeDateAttribute($date)
{
    return $date->format('m-d');
}

// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
Other good practices
Never put any logic in routes files.

Labels: ,

© copyright-2020 Rejaul