Reader Stacks

Creating Custom Classes, Libraries, and Helper Functions in Laravel

A custom class belongs in app/, autoloaded automatically by Composer's PSR-4 mapping — a global helper function needs one extra step: registering its file explicitly in composer.json.

Laravel doesn't restrict application code to models and controllers — a plain custom class (a "library," in older PHP parlance) or a global helper function are both common patterns for logic that doesn't naturally belong in Eloquent or a specific controller.

Creating a custom class

// app/Services/PriceCalculator.php
namespace App\Services;

class PriceCalculator
{
    public function calculateTotal(float $price, int $quantity, float $taxRate = 0.08): float
    {
        $subtotal = $price * $quantity;
        return round($subtotal + ($subtotal * $taxRate), 2);
    }
}
use App\Services\PriceCalculator;

class OrderController extends Controller
{
    public function store(Request $request, PriceCalculator $calculator)
    {
        $total = $calculator->calculateTotal($request->price, $request->quantity);
    }
}

Placing a custom class anywhere under app/ (conventionally in a Services/, Support/, or similarly named subdirectory) means it's automatically autoloaded by Composer's PSR-4 mapping — no manual registration is needed, and Laravel's service container can inject it directly into a controller's constructor or method, as shown.

Creating a custom helper function

// app/helpers.php
if (! function_exists('format_currency')) {
    function format_currency(float $amount, string $currency = 'USD'): string
    {
        return match ($currency) {
            'USD' => '$' . number_format($amount, 2),
            'EUR' => number_format($amount, 2) . ' €',
            default => number_format($amount, 2),
        };
    }
}

Registering the helper file so it actually loads

// composer.json
"autoload": {
    "files": [
        "app/helpers.php"
    ]
}
composer dump-autoload

Unlike a class, a global helper function file needs to be explicitly listed under autoload.files in composer.json — Composer's PSR-4 autoloading only maps class names to file paths, it doesn't automatically scan arbitrary files for loose functions, which is why this manual step and a subsequent dump-autoload are both required.

Using the helper anywhere in the app

{{ format_currency($product->price) }}

Once registered, the function is available globally — in Blade templates, controllers, or anywhere else in the application — without needing a use import statement, the same way Laravel's own built-in helpers like collect() or route() are used.

Why the function_exists() check matters

Wrapping the function definition in if (! function_exists('format_currency')) prevents a fatal "Cannot redeclare function" error if the helpers file is ever accidentally loaded more than once (which can genuinely happen depending on autoloading configuration and package interactions) — a defensive habit worth keeping for any globally-defined function.

Choosing a class vs. a helper function

A class suits logic that has meaningful state or benefits from dependency injection (like the PriceCalculator example, which could take a tax-rate-lookup service as a constructor dependency) — a helper function suits small, pure, stateless utilities used in many places, especially in Blade templates where instantiating a class via the container is more cumbersome than just calling a global function directly.