7 tips to stay productive with Laravel
Tags: webdev, laravel, php, productivity
Published: 2019-03-16 00:32:38

---
title: 7 tips to stay productive with Laravel
published: true
description: Learn how I stay productive with Laravel.
tags: webdev, laravel, php, productivity
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ui9rooe2m8e1kzd1f3n5.jpg
---

Laravel inner power resides in details. Let me give you these 7 tips to keep your code maintainable.

- [1. Controllers should... well, control](#1-controllers-should-well-control)
- [2. Cleaner validation with Requests Forms](#2-cleaner-validation-with-form-requests)
- [3. Type hint your models in your controllers](#3-type-hint-your-models-in-your-controllers)
- [4. Centralized error handling](#4-centralized-error-handling)
- [5. Collections... collections everywhere](#5-collections-collections-everywhere)
- [6. Factorize your routes with Resources](#6-factorize-your-routes-with-resources)
- [7. Lean code with mutators](#7-lean-code-with-mutators)

# 1. Controllers should... well, control

Ever seen/wrote a big boy like this?

```php
use Validator;
use Kreait\Firebase\Factory;
use Kreait\Firebase\ServiceAccount;

class AuthenticationController extends Controller {
  public function index(Request $request) {
    if ($request->wantsJson()) {
      $validator = Validator::validate($request->all(), [
        'token' => 'required|filled|string'
      ]);
  
      if ($validator->fails()) {
        return response()->json('wrong token', 400);
      }

      $serviceAccount = ServiceAccount::fromArray(config('firebase'));
      $firebase = (new Factory)->withServiceAccount($serviceAccount)
        ->create()
        ->getAuth();

      try {
        $firebase->verifyIdToken($token);
  
        return response()->json('ok');
      } catch(Exception $exception) {
        app('sentry')->captureException($exception);

        return response()->json('unauthorized', 403);
      }
    } else {
      abort(403);
    }
  }
}
```

What about this:

```php
use App\Firebase;
use App\Http\Requests\AuthenticationRequest;

class AuthenticationController extends Controller {
  public function index(AuthenticationRequest $request) {
    if ($request->wantsJson()) {
      if (Firebase::tokenValid($request->input('token'))) {
        return response()->json('ok');
      } else {
        return response()->json('unauthorized', 403);
      }
    } else {
      abort(403);
    }
  }
}
```

We use several things to help us free the business logic out of the rest. I will cover [Request forms](https://laravel.com/docs/5.8/validation#creating-form-requests), which helped me here to remove the validation step out of the controller, and [Error handling](https://laravel.com/docs/master/errors) in this post.

But must important, **do not put business logic in your controllers**. [Controllers](https://laravel.com/docs/master/controllers) should remain light and only use the tools it needs to take decisions on how to reroute the request. In the end, it is just a request controller, nothing more. Any business decisions should be located on separated [Models](https://laravel.com/docs/master/eloquent).

And yes, you can create classes that are not necessarily related to a table. In my example, I **modelized** a "problem" using a class: the Firebase authentication.

# 2. Cleaner validation with Form requests

I love validation with Laravel. I like the clean and expressive way they are used. Typically an example of validating a user cart.

```php
use App\Product;
use App\Order;
use App\Session;

class CartController extends Controller {
  public function store(Request $request) {
    $request->validate([
      'id' => 'required|exists:product,id'
    ]);

    // Request passed, time to save the data

    $productId = $request->input('id');
    $product = Product::find($productId);

    Order::insert([
      'productId' => $product->id
    ]);

    Session::flash('success', 'Order completed!');

    return response()->view('product');
  }
}
```

This is nice, but we could simplify more. First you will need to create a Form Request.

```bash
php artisan make:request CartRequest
```

This will create a file on `app/Http/Requests/CartRequest.php`. Open it, it looks like this:


```php
use Illuminate\Foundation\Http\FormRequest;

class CartRequest extends FormRequest {
  public function authorize() {
    return true; // Unless you have Guards, set it to true.
  }

  public function rules() {
    return [
      'id' => 'required|exists:product,id'
    ];
  }
}
```

So now we can use it in our controller:

```php
use App\Product;
use App\Order;
use App\Session;
use App\Http\Requests\CartRequest;

class CartController extends Controller {
  public function store(CartRequest $request) {
    // The request is validated using our CartRequest

    $productId = $request->input('id');
    $product = Product::find($productId);

    Order::insert([
      'productId' => $product->id
    ]);

    Session::flash('success', 'Order completed!');

    return response()->view('product');
  }
}
```

This is a great way to strip up the fuzz and focus on the business algorithm. And the best, you can **reuse** form requests for models that share similar validation.


```php
use App\Http\Requests\ProductRequest;

class Shoe extends Controller {
  public function index(ProductRequest $request) {
    // ...
  }
}

class Clothing extends Controller {
  public function index(ProductRequest $request) {
    // ...
  }
}
```

![HAPPY ROBERT REDFORD GIF](https://media.giphy.com/media/xSM46ernAUN3y/giphy.gif)

# 3. Type hint your models in your controllers

Sometimes I wrote 3 kilometers long routes, and my controllers follow along...

```php
Route::get('/customer/{customerId}/contract/{contractId}/invoice/{invoiceId}', 'CustomerContractInvoiceController@show');
```

```php
use App\Customer;
use App\Contract;
use App\Invoice;

class CustomerContractInvoiceController extends Controller {
  public function show(Request $request, int $customerId, int $contractId, int $invoiceId) {
    $customer = Customer::findOrFail($customerId);
    $contractId = Contract::findOrFail($contractId);
    $invoice = Invoice::findOrFail($invoiceId);

    // ...
  }
}
```

Laravel can handle the model fetching for you using [Route model binding](https://laravel.com/docs/5.8/routing#route-model-binding). Resistance is futile...

```php
use App\Customer;
use App\Contract;
use App\Invoice;

class CustomerContractInvoiceController extends Controller {
  public function show(Request $request, Customer $customer, Contract $contract, Invoice $invoice) {
    $customerName = $customer->name;

    // ...
  }
}
```

This way you can directly use the Eloquent models. To do this, Laravel needs you to properly specify the `$primaryKey` property on your model in case this is not `id`:

```php
use Illuminate\Database\Eloquent\Model;

class Customer extends Model {
  protected $primaryKey = 'customerId';
}

```

# 4. Centralized error handling

I am exhausted of having to check for errors in my controllers.

```php
use Validator;
use Session;

class NewsletterController extends Controller {
  public function store(Request $request) {
    $validator = new Validator($request->all(), [
      'email' => 'required|email'
    ]);

    if ($validator->fails) {
      return redirect()->back()->withInput()->withErrors($validator);
    }

    Session::flash('success', 'We publish each week so stay tuned!');

    return redirect()->route('newsletter.success');
  }
}
```

So I configure my global [Error handling](https://laravel.com/docs/master/errors) to take advantage of exceptions thrown by Form Requests. To do so, go to `app/Exceptions/Handler.php`.

```php
class Handler extends ExceptionHandler {
  public function report(Exception $exception) {
    // This is where you log to your error dashboard like Sentry, ...

    parent::report($exception);
  }

  public function render($request, Exception $exception) {
    // Here we manage errors globally
  }
}
```

So the method that we want to customize is `render`. Report is great to push a notification to an error management app like [Sentry](https://sentry.io).

Let us handle validation errors globally.

```php
use Illuminate\Validation\ValidationException;

class Handler extends ExceptionHandler {
  public function report(Exception $exception) {
    // This is where you log to your error dashboard like Sentry, ...

    parent::report($exception);
  }

  public function render($request, Exception $exception) {
    if ($exception instanceof ValidationException) {
      return redirect()->back()->withInput()->withErrors($exception->errors());
    }
  }
}
```

You can even make it manage navigation and AJAX errors in a single shot.

```php
use Illuminate\Validation\ValidationException;

class Handler extends ExceptionHandler {
  public function report(Exception $exception) {
    // This is where you log to your error dashboard like Sentry, ...

    parent::report($exception);
  }

  public function render($request, Exception $exception) {
    // The request have a "Accept: application/json"
    // This is an AJAX request
    if ($request->wantsJson()) {
      if ($exception instanceof ValidationException) {
        return response()->json($exception->errors(), 400);
      }
    }
    // This is a normal form validation 
    else {
      if ($exception instanceof ValidationException) {
        return redirect()->back()->withInput()->withErrors($exception->errors());
      }      
    }
  }
}
```

![BEST GIF THUMBS UP GIF](https://media.giphy.com/media/111ebonMs90YLu/giphy.gif)

# 5. Collections... collections everywhere

What I regret with PHP is the lack of OOP. Javascript is so cool with its POP. 

```javascript
const popularPosts = posts.filter(post => post.views > 5000)->map(post => post.title);
```

In Laravel, one of the best feature is Collections. Eloquent models becomes so handy and we can perform a lot of things fluently.

```php
use App\Post;

$popularPosts = Post::all()->filter(function($post) { 
  return $post->views > 5000;
})->map(function($post) {
  return $post->title;
});
```

And you can even use it outside Eloquent usage!

```php
$menus = [
  [ 'placement' => 'left', 'text' => 'Home' ],
  [ 'placement' => 'left', 'text' => 'About us' ],
  [ 'placement' => 'right', 'text' => 'Contact' ]
];

$rightMenus = collect($menus)->filter(function($menu) {
  return $menu['placement'] === 'right';
});
```

# 6. Factorize your routes with resources

I do not know if it happened to you, but one time I built a web app at my job (still in production), and my `routes/web.php` became to look like this:

```php
// routes/web.php

Route::get('/customer', 'CustomerController@index');
Route::get('/customer/create', 'CustomerController@create');
Route::get('/customer/{id}', 'CustomerController@show');
Route::get('/customer/{id}/edit', 'CustomerController@edit');
Route::get('/customer/{id}/delete', 'CustomerController@delete');
Route::get('/customer/{id}/phone', 'CustomerPhoneController@index');
Route::get('/customer/{id}/phone/create', 'CustomerPhoneController@create');
Route::get('/customer/{customerId}/phone/{phoneId}', 'CustomerPhoneController@show');
Route::get('/customer/{customerId}/phone/{phoneId}/edit', 'CustomerPhoneController@edit');

Route::post('/customer', 'CustomerController@store');
Route::post('/customer/{id}/phone', 'CustomerPhoneController@store');

Route::put('/customer/{id}', 'CustomerController@update');
Route::put('/customer/{customerId}/phone/{phoneId}', 'CustomerPhoneController@update');

Route::delete('/customer/{id}', 'CustomerController@destroy');
Route::delete('/customer/{customerId}/phone/{phoneId}', 'CustomerPhoneController@destroy');
```

... And this is an excerpt. I also manage customer addresses, customer emails, customer companies, customer contracts, customer contracts invoices, ...

![OH NO OOPS GIF BY HALLMARK CHANNEL
](https://media.giphy.com/media/628gjKuwSNGkX9inxX/giphy.gif)

Fortunately Laravel have our backs with [Resources controllers](https://laravel.com/docs/5.8/controllers#resource-controllers).

```php
// routes/web.php

Route::resource('customer', 'CustomerController');
Route::resource('customer.phone', 'CustomerPhoneController');
```

Finally some fresh air...

Resources works by automatically binding your first argument to the 4 common HTTP methods. In fact if you run 

```bash
php artisan route:list
```

You would have the following output.

| Domain | Method    | URI                                     | Name                   | Action                                               | Middleware |
|--------|-----------|-----------------------------------------|------------------------|------------------------------------------------------|------------|
|        | GET,HEAD  | /customer                               | customer.index         | App\Http\Controllers\CustomerController@index        | web        |
|        | GET,HEAD  | /customer/{customer}                    | customer.show          | App\Http\Controllers\CustomerController@show         | web        |
|        | GET,HEAD  | /customer/{customer]/edit               | customer.edit          | App\Http\Controllers\CustomerController@edit         | web        |
|        | GET,HEAD  | /customer/{customer}/phone              | customer.phone.index   | App\Http\Controllers\CustomerPhoneController@index   | web        |
|        | GET,HEAD  | /customer/{customer/phone/{phone}       | customer.phone.show    | App\Http\Controllers\CustomerPhoneController@show    | web        |
|        | GET,HEAD  | /customer/{customer}/phone/{phone}/edit | customer.phone.edit    | App\Http\Controllers\CustomerPhoneController@edit    | web        |
|        | POST      | /customer                               | customer.store         | App\Http\Controllers\CustomerController@store        | web        |
|        | POST      | /customer/{customer}/phone              | customer.phone.store   | App\Http\Controllers\CustomerPhoneController@store   | web        |
|        | PUT,PATCH | /customer/{customer}                    | customer.update        | App\Http\Controllers\CustomerController@update       | web        |
|        | PUT,PATCH | /customer/{customer}/phone/{phone}      | customer.phone.update  | App\Http\Controllers\CustomerPhoneController@update  | web        |
|        | DELETE    | /customer/{customer}                    | customer.destroy       | App\Http\Controllers\CustomerController@destroy      | web        |
|        | DELETE    | /customer/{customer}/phone/{phone}      | customer.phone.destroy | App\Http\Controllers\CustomerPhoneController@destroy | web        |

And again, Laravel got your covered, so you can use this command line to create your resource controllers:

```bash
php artisan make:controller CustomerController --resource
```

Which will automatically create the methods above for you!

```php
// app/Http/Controllers/CustomerController.php

use Illuminate\Http\Request;

class CustomerController extends Controller
{
  public function index() {}

  public function create() {}

  public function store(Request $request) {}
  
  public function show($id) {}
  
  public function edit($id) {}
  
  public function update(Request $request, $id) {}
  
  public function destroy($id) {}
}

```

And... if you create a model, you can also create its associated controller in resource mode, so that Laravel will type hint your controller for you.

```bash
php artisan make:model Customer --resource --controller
```

```php
// app/Http/Controllers/CustomerController.php

use App\Customer;
use Illuminate\Http\Request;

class CustomerController extends Controller
{
  public function index() {}
    
  public function create() {}
    
  public function store(Request $request) {}
    
  public function show(Customer $customer) {}
    
  public function edit(Customer $customer) {}
    
  public function update(Request $request, Customer $customer) {}
    
  public function destroy(Customer $customer) {}
}
```

![Productivity level: over 9000 meme](https://i.imgflip.com/2w5ttm.jpg)

# 7. Lean code with mutators

Sometimes, mixing multiple field of a record is useful to create meaningful content. For example, in a customer view:

```html
<!-- resources/views/customer/show.blade.php -->

@extends('layout/logged')
@section('content')
  <h1>viewing customer {{ $customer->firstName }} {{ $customer->lastName }}</h1>
@endsection
```

Mutators will help you abstracting your business logic.

```php
// app/Customer.php

use Illuminate\Database\Eloquent\Model;

class Customer extends Model {
  protected $table = 'customer';

  public function getNameAttribute() {
    return "{$this->firstName} {$this->lastName}";
  }
}
```

To have access to a `$customer->name` attribute, you need to create a function like above, in the form `public function get<yourattribute>Attribute() {}`. Laravel will then provide you this attribute when you collect an Eloquent model.

```html
<!-- resources/views/customer/show.blade.php -->

@extends('layout/logged')
@section('content')
  <h1>{{ $customer->name }}</h1>
@endsection
```

Even better, you can also cast the data comming from your database.

Imaging you have a boolean field in your customer table named `wantsNewsletter`. As you know, boolean type in MySQL does not exists, so it simulates it with this column:

```sql
wantsNewsletter TINYINT(1) NOT NULL DEFAULT 0
```

0 for `false`, 1 for `true`. In your eloquent model, you can ask Laravel to cast it to a boolean:

```php
// app/Customer.php

use Illuminate\Database\Eloquent\Model;

class Customer extends Model {
  protected $table = 'customer';
  protected $casts = [
    'wantsNewsletter' => 'boolean'
  ];

  public function getNameAttribute() {
    return "{$this->firstName} {$this->lastName}";
  }
}
```

Now you can use a triple equal operator to perform your verification.

```php
// app/Console/Commands/SendNewsletter.php

use Illuminate\Console\Command;
use App\Mail\NewsletterMail;
use App\Customer;

class SendNewsletter extends Command {
  protected $signature = 'newsletter:send';

  public function handle() {
    $customers = Customer::all();

    $customers = $customers->filter(function($customer) {
      $customer->wantsNewseletter === true;
    });

    foreach($customers as $customer) {
      Mail::to($customer->email)->send(new NewsletterMail($customer));
    }
  }
}
```

# Conclusion

All of this can be overwhelming. Do not apply those in a row. Try to find a balance between what you feel is right and what you can do to improve your code base. 

Adapting a quote found from [Vuex website](https://vuex.vuejs.org/):

> These tips are like glasses: You will know when you need it.

I hope you are as amazed as I was when I learn those techniques. Tell me in comments if you learned something new, if you have others tips I did not listed in, or if I can do better with those tips.

Happy optimizations!

Photo by [nappy](https://www.pexels.com/@nappy) from [Pexels](https://www.pexels.com)