Laravel

Introduction

This is a guide to maintain a consistent and industry stand code in Laravel.

Laravel Routes

  • Meaningful / Predictable
  • All small letter
  • 2 words must be joined with hyphen (-)

This is an example of for articles

CRUD Routes

HTML listing page

GET /articles/index
Content-Type: application/xhtml+xml
Controller-Method: ArticleController@index
Model-Method: ""

Listing

GET /articles
Content-Type: application/json
Controller-Method: ArticleController@getList
Model-Method: Article::getList

Create one or many records

POST /articles
Content-Type: application/json
Controller-Method: ArticleController@createList
Model-Method: Article::createList

If an array is passed, multiple records will be created or else single record will be created.

Eg: Single Record

$inputs = [
  "first_name" => 'John'
  "last_name" => 'Leo'
]

Eg: Multiple Records

$inputs = [
  [
  "first_name" => 'John'
  "last_name" => 'Leo'
  ],
  [
  "first_name" => 'David'
  "last_name" => 'Leo'
  ]
]

Bulk Update (eg: bulk status change)

PUT /articles
PATCH /articles
Content-Type: application/json
Controller-Method: ArticleController@updateList
Model-Method: Article::updateList

Bulk Delete

DELETE /articles
Content-Type: application/json
Controller-Method: ArticleController@deteleList
Model-Method: Article::deteleList

Read a record

GET /articles/1
Content-Type: application/json
Controller-Method: ArticleController@getItem
Model-Method: Article::getItem

Update a record (update, soft delete, status change etc)

PUT /articles/1
PATCH /articles/1
Content-Type: application/json
Controller-Method: ArticleController@updateItem
Model-Method: Article::updateItem

Delete a record (hard deleted)

DELETE /articles/1
Content-Type: application/json
Controller-Method: ArticleController@deteleItem
Model-Method: Article::deteleItem

Fetch a relation of a record

GET /articles/1/relationships/author
Content-Type: application/json
Controller-Method: ArticleController@getItemAuthor
Model-Method: Article::getItemAuthor

Attach a relation to a record

POST /articles/1/relationships/author
Content-Type: application/json
Controller-Method: ArticleController@attachItemAuthor
Model-Method: Article::attachItemAuthor

Update a relation to a record

PUT /articles/1/relationships/author
PATCH /articles/1/relationships/author
Content-Type: application/json
Controller-Method: ArticleController@updateItemAuthor
Model-Method: Article::updateItemAuthor

Delete a relation to a record

DELETE /articles/1/relationships/author
Content-Type: application/json
Controller-Method: ArticleController@deleteItemAuthor
Model-Method: Article::deleteItemAuthor

Fetch list of relations

GET /articles/1/relationships/comments
Content-Type: application/json
Controller-Method: ArticleController@getItemComments
Model-Method: Article::getItemComments

Update list of relations

PUT /articles/1/relationships/comments
PATCH /articles/1/relationships/comments
Content-Type: application/json
Controller-Method: ArticleController@updateItemComments
Model-Method: Article::updateItemComments

Delete list of relations

DELETE /articles/1/relationships/comments
Content-Type: application/json
Controller-Method: ArticleController@deleteItemComments
Model-Method: Article::deleteItemComments

Include relations

GET /articles?include=comments
Content-Type: application/json
Controller-Method: ArticleController@getList
Model-Method: Article::getList

Include relation's relations

GET /articles?include=comments.created_by
Content-Type: application/json
Controller-Method: ArticleController@getList
Model-Method: Article::getList

Sorting

GET /articles?sort=title,created_by
Content-Type: application/json
Controller-Method: ArticleController@getList
Model-Method: Article::getList
Sorting in descending order of created_by, use :desc in front of parameter
GET /articles?sort=title,created_by:desc
Content-Type: application/json
Controller-Method: ArticleController@getList
Model-Method: Article::getList

Performance Tuning SQL Queries

SQL tuning is the process of improving SQL queries to accelerate your servers performance. It's general purpose is to reduce the amount of time it takes a user to receive a result after issuing a query, and to reduce the amount of resources used to process a query.

Big SQL queries can suffer performance issues. Performance degradation often occurs if the database is not being properly maintained or if queries can be rewritten more efficiently.

Following Query optimization tips for better performance

Apply Index On Necessary Columns:

Table indexes in databases help retrieve information faster and more efficiently. An index creates a unique data column without overlapping each other. It improves the speed of data retrieval.

Avoid Query In A Loop:

If sometimes we need to execute the same query multiple times like if we need to insert 10 records in any table at that time don’t use insert query in the loop instead of using bulk insert.

We shouldn't call Eloquent relationships method in loop. First convert model object in to collection and then pass in to loop.

Wrong Examples:

public function category()
{
    return $this->belongsTo(Category::class,
         'catgeory_id', 'id'
    );
}
 
 
public function getList()
{

    $products = Product::with(['catgory'])->all();

    foreach ($products as $key => $product)
    {

        if($product && $product->category){

        }
    
    }

}
    

Correct Examples:


public function category()
{
    return $this->belongsTo(Category::class,
        'catgeory_id', 'id'
    );
}
     
public function getList()
{

    $products = Product::with(['catgory'])->all();
    
    $collect_products = collect($products);
    
    foreach ($collect_products as $key => $product)
    {
        if($product && $product->category){

        }
    }

}

Avoid too many JOINs:

When you add multiple tables to a query and join them, you may overload it. In addition, a large number of tables to retrieve data from may result in an inefficient execution plan. When generating a plan, the SQL query optimizer needs to identify how the tables are joined, in which order, how and when to apply filters and aggregation.

JOIN elimination is one of the many techniques to achieve efficient query plans. You can split a single query into several separate queries which can later be joined, and thus remove unnecessary joins, subqueries, tables, etc.

Use SELECT fields instead of retrieve all data:

The SELECT statement is used to retrieve data from the database. In the case of large databases, it is not recommended to retrieve all data because this will take more resources on querying a huge volume of data.

If we execute the following query, we will retrieve all data from the Users table, including, for example, users’ avatar pictures. The result table will contain lots of data and will take too much memory and CPU usage.

BAD CODE


User::get();
   

Instead, you can specify the exact columns you need to get data from, thus, saving database resources. In this case, SQL Server will retrieve only the required data, and the query will have lower cost.

GOOD CODE


User::select('first_name','last_name','email')->get();
   

Use Exists instead of First or Count in condition :

Instead of using the count method to determine if any records exist that match your query's constraints, you may use the exists and doesntExist methods.

It depends if you want to work with the user afterwards or only check if one exists.

If you want to use the user object if it exists:


$user = User::where('email', '=', Input::get('email'))->first();
if ($user === null) {
  // user doesn't exist
}
   

And if you only want to check


if (User::where('email', '=', Input::get('email'))->count() > 0) {
   // user found
}
   

Or even nicer


if (User::where('email', '=', Input::get('email'))->exists()) {
  // user found
}
   

Query Operators

MySql OperatorQuery ParameterPurpose
=eqEqual to
!=notNot equal to
<ltLess than
<=lteLess than or equal to
>gtGreater than
>=gteGreater than or equal to
likelikeLike search query
betweenbtwBetween

API Routes

Get list of articles with pagination


GET /api/articles

Specify page number

GET /api/articles?page=5

Limit the results

GET /api/articles?limit=10

Sort By ascending order of title then created_by

GET /api/articles?sort=title,created_by

Sort By in ascending order of title then descending order created_by

GET /api/articles?sort=title,created_by:desc,price

Include comment in the article (relationship)

GET /api/articles?include=comments

Include comment with created_by relationship

GET /api/articles?include=comments.created_by

Examples of filters with operators

GET /api/articles?filter[is_active]=true
GET /api/articles?filter[created_by]=eq:5
GET /api/articles?filter[title]=like:hello
GET /api/articles?filter[price]=gt:5
GET /api/articles?filter[date]=btw:5|7

GET /api/articles?filter[is_active]=true&filter[created_by]=eq:5&filter[date]=btw:5|7

Multiple operators

GET /api/articles?filter[price]=gt:5|AND|lt:10

Get single record of article

GET /api/articles/1

To create single article

POST /api/articles

Update single record of article

PUT /api/articles/1
PATCH /api/articles/1

Delete single record of article

DELETE /api/articles/1

Get single record of article with it's author

GET /api/articles/1/relationships/author

Attach author (relationship) to the article

POST /api/articles/1/relationships/author

Update author (relationship)

PUT /api/articles/1/relationships/author
PATCH /api/articles/1/relationships/author

Delete author (relationship)

DELETE /api/articles/1/relationships/author

Get article with multiple comments (relationships)

GET /api/articles/1/relationships/comments

Create/Attach comments (relationships)

POST /api/articles/1/relationships/comments

Update article with multiple comments (relationships)

PUT /api/articles/1/relationships/comments
PATCH /api/articles/1/relationships/comments

Delete multiples comments (relationships)

DELETE /api/articles/1/relationships/comments

Laravel Community Guidelines for Good Coding Practices

Shorter and readable syntax

  • Use shorter and more readable syntax where possible

Bad Examples

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

Good Example

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

More Examples

Common syntaxShorter 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->idoptional($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')

Avoid Fat Methods

  • Avoid fat controllers and write frequent queries in model.
  • DB related logic should be in Eloquent models or in Repository/Helper classes.
  • Do not include business logic in Controllers. They should be in Repository/Helper classes.
  • Methods and Classes should adhere to the Single Responsibility Principle.
  • Don't use extensive if-else nesting. Instead, check for negative conditions first.
  • Do not keep commented code in methods or classes.

Bad Examples

public static function getItem($id)
{
    
    if(!Auth::user()->hasPermission('hrc-can-read-customers')
        && !EqCustomerUser::hasPermission(\Auth::user(),'hrc-can-read-customers')) // check Permissions
    {
        if(Auth::user()){
            $item = self::where('id', $id)
            ->withTrashed()
            ->first();
    
    
            $response['status'] = 'success';
            $response['data'] = $item;
        
            return $response;
        }
        else{
            $response['status'] = 'failed';
            $response['errors'][] = 'User not found';
    
            return $response;
        }
    }
    else{
        $response['status'] = 'failed';
        $response['errors'][] = trans("vaahcms::messages.permission_denied");
        return $response;
    }
    
    // $user = Auth::user();
    // if(!$user) {
    //    $response['status'] = 'failed';
    //    $response['errors'][] = 'User not found';
    //
    //    return $response;
    // }

}

Good Examples

// Add this method to Helper/Repository class
public static checkPermission($permission_slug){
    if(!Auth::user()->hasPermission('hrc-can-read-customers')){
        $response['status'] = 'failed';
        $response['errors'][] = trans("vaahcms::messages.permission_denied");
        return $response;
    }
    
    $response['status'] = 'success';
    $response['messages'][] = 'Action is successful';
}


// this code will be in model
public static function getItem($id)
{
    $permission_response = Helper::checkPermission('hrc-can-read-customers');
    if($permission_response['status'] !== 'success') return $permission_response;
    
    $user = Auth::user();
    if(!$user) {
        $response['status'] = 'failed';
        $response['errors'][] = 'User not found';

        return $response;
    }

    $item = self::where('id', $id)
        ->withTrashed()
        ->first();

    $response['status'] = 'success';
    $response['data'] = $item;
    return $response;
}

Prefer to use Eloquent over raw SQL queries

  • Prefer to use Eloquent over using Query Builder and raw SQL queries. Prefer collections to arrays.
  • Eloquent allows you to write readable and maintainable code. Also, Eloquent has great built-in tools like soft deletes, events, scopes etc.

Bad Example

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 Example

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