When I first started learning Laravel, I only used models, views, and controllers to build a full-stack project. At the time, I felt that was already effective enough to write good code. After a year of getting used to Laravel during college, I started looking for Laravel-based job opportunities.

Once I entered the professional world, Laravel felt overwhelming to me. There were so many unfamiliar features that I found difficult to grasp at first. I often asked myself, “Why use this approach when we can just handle it in the controller?” and other similar questions.

However, as I continued to use it and pushed through the initial complexity, I realized that these features actually help us make the most of what the framework has to offer.
Here are some of the features I use most often and feel familiar enough with to explain.

Helpers

I usually use helpers to define variables that are frequently used or to handle simple data transformations, such as formatting prices, currency, payment status values, and so on.

Helpers — Response JSON

				
					namespace App\Helpers;

class ResponseHelper
{
    // Response Json
    public static function simpleResponse($message, $success = true, $status = 200)
    {
        return response()->json([
            'success' => $success,
            'message' => $message,
        ], $status);
    }
}
				
			

Here is an example of its usage in the controller.

				
					namespace App\Http\Controllers\API;

use App\Helpers\ResponseHelper;

class ActivityApiController extends Controller
{
    // some code

    if($generalSetting->cod == 1 && $cod_reservation > $date_selected) {
       return ResponseHelper::simpleResponse("You cannot place an order within 24 hours. Please choose a date starting from tomorrow.", false, 422);
    }
}
				
			

Helpers — Variabel Status Payment

				
					namespace App\Helpers;

class PaymentStatusHelper
{
    // status payment
    const XENDIT = [
        'PENDING' => 'PENDING',
        'PAID' => 'PAID',
        'EXPIRED' => 'EXPIRED'
    ];
}
				
			

Here is an example of its usage in the controller.

				
					namespace App\Http\Controllers\API;

use App\Helpers\PaymentStatusHelper;


class XenditApiController extends Controller
{
    public function webhook(Request $request, $type)
    {
        if($status == PaymentStatusHelper::XENDIT['PAID']) {
            // some code      
        }
    }
}
				
			

Middleware

I usually use middleware for authentication, to distinguish between pages for users who are already logged in and those who are not. Here’s an example:

				
					namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class CheckLogin
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure(\Illuminate\Http\Request): (\Illuminate\Http\Response|\Illuminate\Http\RedirectResponse)  $next
     * @return \Illuminate\Http\Response|\Illuminate\Http\RedirectResponse
     */
    public function handle(Request $request, Closure $next)
    {
        if (!session()->has('role') && !session()->has('id')) {
            return redirect()->route('login');
        }

        return $next($request);
    }
}
				
			

Then I registered my middleware in kernel.php

				
					namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    protected $routeMiddleware = [
        // some middleware
        'check.login' => \App\Http\Middleware\CheckLogin::class
    ];
}
				
			

After that, I added it in web.php.

				
					Route::middleware(['check.login'])->group(function () {
    // some code
});
				
			

Requests

I made a request to perform input validation from the user. Here’s a sample of the code.

				
					namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class AdditionalDiscountRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            "disc_name" => "required|string",
            "disc" => "required|numeric",
            "days" => "required|numeric",
            "pricing_code" => 'required|array',
        ];
    }

     /**
     * Filter input sebelum digunakan di controller.
     */
    public function filtered()
    {
        return $this->except(['_token', '_method']);
    }
}
				
			

After that I call the validation request that was created into the controller.

				
					namespace App\Http\Controllers;

use App\Http\Requests\AdditionalDiscountRequest;


class AdditionalDiscountController extends Controller
{
    /**
     * Store a newly Additional Discount
     */
    public function store(AdditionalDiscountRequest $request)
    {
        $this->addDiscountService->createAdditionalService($request->filtered());
        Alert::success('Sukses!', 'Data telah berhasil ditambahkan.');

        return redirect()->route('corporate.additional.discount.index');
    }
}
				
			

Email

after that I call the request validation that is made into the controller. I usually use this feature to send invoices or notification messages. At first it was quite complicated to make, but after I was able to create one function, I was able to use it to create other emails 😂. At first I will set .env first, here is an example of the code

				
					MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=test@gmail.com
MAIL_PASSWORD=jpjxjjpurpoxgehx
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
				
			

then i created the email

				
					namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;

class InvoiceMail extends Mailable
{
    use Queueable, SerializesModels;
    public $clientName, $subject, $pdfPath, $fileName;

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($clientName, $subject, $pdfPath, $fileName)
    {
        $this->clientName = $clientName;
        $this->subject = $subject;
        $this->pdfPath = $pdfPath;
        $this->fileName = $fileName;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->view('emails.payment-cc')
        ->subject($this->subject)
        ->with([
            'clientName' => $this->clientName
        ])
        ->attach($this->pdfPath, [
            'as' => $this->fileName,
            'mime' => 'application/pdf',
        ]);
    }
}
				
			

After the email is done, I create the email view

				
					Dear {{ $clientName }},<br><br>
You have received a booking. Please check an attachment to see the details.<br><br>
Thank You,<br>
<strong>Admin</strong>
				
			

then i will use this email in controller

				
					public function sendEmailToAdmin($email, $clientName, $pdfPath, $file_name)
{
    Mail::to($email)->send(new InvoiceMail(
      $clientName,
      'Invoice',
      $pdfPath,
      $file_name
    ));
}
				
			

Service

I use this Laravel feature to separate processes that require logic or calculations with the process flow. I still put the process flow in the controller, but I separate the logical process into a service.

Previously, I was a bit confused about this service concept, because the code looked very complex because it was usually spread across several services and called in one controller, but on the other hand, this method can make the code reusable because it can be called in many existing controllers with just one code that I put in the service.

To make it, first I created the service

				
					namespace App\Services;

use App\Helpers\ResponseHelper;
use App\Models\PaymentInstapay;
use App\Services\Contracts\IInstapayService;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;

class InstapayService implements IInstapayService
{
    /**
     * Create invoice and get link payment
     */
    public function createInvoice($reservation_code, array $instapay, $reservationId)
    {
        try {
            $baseURL = config('app.instapay.endpoint');

            $secretKey = config('app.instapay.secret_key');
            $clientKey = config('app.instapay.client_key');

            $base64Token = base64_encode($clientKey . ':' . $secretKey);

            $paymentInstapay = PaymentInstapay::create([
                'reservation_id' => $reservationId,
            ]);

            $response = Http::withHeaders([
                'Authorization' => 'Basic ' . $base64Token,
                'Content-Type' => 'application/json',
            ])->post($baseURL, [
                'order_id' => $this->uniqOrderIdInstapay(),
                'amount' => $instapay['amount'],
                'description' => $instapay['description'],
                'customer_details' => $instapay['customer_details'],
                'callback_url' => $instapay['callback_url'],
                'success_redirect_url' => $instapay['success_redirect_url'],
                'failed_redirect_url' => $instapay['failed_redirect_url'],
                'admin_fee_paid_by_customer' => $instapay['admin_fee_paid_by_customer'],
            ]);

            if(!$response->json('public_id')) {
                throw new HttpResponseException(ResponseHelper::simpleResponse('Payment process failed, an error occurred in the payment gateway.', false, 422));
            } else {
                $paymentInstapay->update([
                    'public_id' => $response->json('public_id')
                ]);

                info(['link payment instapay', $response->json()]);

                return $response->json('invoice_url');
            }

        } catch (HttpResponseException $e) {
            throw new HttpResponseException(ResponseHelper::simpleResponse('Payment process failed, an error occurred in the payment gateway.', false, 422));
        }
    }

    /**
     * Uniq id order instapay
     */
    public function uniqOrderIdInstapay(): string
    {
        return str_replace(['-', ' '], ['_', '.'], Str::uuid()->toString());
    }
}
				
			

then I create the contract. Usually the contract is created first, but I am more comfortable creating the contract after the service is created. Actually, this contract is used so that the service that is created has a parameter data type and its return does not deviate from the expected provisions. Here is the code

				
					namespace App\Services\Contracts;

interface IInstapayService
{
    public function createInvoice($reservation_code, array $instapay_data, $reservationPaymentId);
}
				
			

In order for the service to be accessible, we need to register it in the AppServiceProvider.php file along with its contract.

				
					namespace App\Providers;

use App\Services\Contracts\IInstapayService;
use App\Services\InstapayService;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        // service registration list..
        $this->app->bind(IInstapayService::class, InstapayService::class);
    }
}
				
			

After that we can call the code in the controller

				
					namespace App\Http\Controllers\API;

use App\Services\Contracts\IInstapayService;


class ReservationApiController extends Controller
{
    protected $instapayService;

    public function __construct(IInstapayService $instapayService){
        $this->instapayService = $instapayService;
    }

    public function paymentWithInstapay($request, $code, $totalAll, $reservationId)
    {
        $instapay_request = [
            'amount' => (int) ceil($totalAll),
            'description' => "Payment",
            'customer_details' => [
                'full_name' => $request['full_name'],
                'email' => $request['email'],
                'phone' => $request['phone']
            ],
            'callback_url' => config('app.base_url') . '/api/v1/webhook/instapay',
            'success_redirect_url' => config('app.base_url') . '/api/v1/success',
            'failed_redirect_url' => config('app.base_url') . '/api/v1/failed',
            'admin_fee_paid_by_customer' => 'true',
        ];

        return $this->instapayService->createInvoice(
            $code, $instapay_request, $reservationId
        );
    }
}
				
			

Spreads data to all existing views

There are times when I want to share the same data across all views. Instead of repeating the code in the controller, I add it to AppServiceProvider.php

				
					public function boot()
{
    Schema::defaultStringLength(191);
    $user = Users::where('id', session('id'))->first();

    // share data with static way
    View::share('auth', $user);

    // share data to all view with condition
    View::composer('*', function ($view) {
        $userId = session('id');
        $role = session('role');

        if ($role === 'corporate' && $userId) {
            $user = Users::find($userId);

            if ($user) {
                $properties = explode(',', $user->property);
                $activities = Activity::whereIn('act_code', $properties)
                    ->select(['id', 'act_code', 'act_name'])
                    ->get();

                $view->with('activities', $activities);
                $view->with('auth', $user);
            }
        }
    });
}
				
			

Its use in the view can automatically be used as in the following example.

				
					@if(auth)
    <p>Welcome, {{ auth->name }}!</p>
@endif

@if(activities)
    <ul>
        @foreach(activities as activity)
            <li>{{ activity.act_name }}</li>
        @endforeach
    </ul>
@endif
				
			

These are the ones I use most often in my Laravel projects, I hope this info helps!

Thank you for stopping by and reading this far! I hope this article is useful. If you like it, support me on Trakteer🍹 or Ko-Fi ☕.
Keep learning, keep growing! ✨