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.