A Laravel facade — like Cache::get() or Auth::user() — looks like a static method call, but it's actually a proxy that resolves an object from the service container and forwards the call to it. Creating a custom facade for your own class follows the same three-piece pattern.
The underlying class
namespace App\Services;
class OrderCalculator
{
public function calculateTotal(array $items): float
{
return collect($items)->sum(fn ($item) => $item['price'] * $item['quantity']);
}
}
The facade class
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class OrderCalculator extends Facade
{
protected static function getFacadeAccessor(): string
{
return \App\Services\OrderCalculator::class;
}
}
getFacadeAccessor() is the one method every facade must implement — it returns the container binding key (here, just the class name itself) that the facade resolves and forwards calls to.
Using the facade
use App\Facades\OrderCalculator;
$total = OrderCalculator::calculateTotal($items);
This works even without an explicit service container binding, since Laravel's container can resolve App\Services\OrderCalculator::class automatically via reflection (assuming its constructor has no unresolvable dependencies) — a binding becomes necessary once the class needs specific configuration or should be bound as a singleton.
Binding it explicitly, for a singleton or configured instance
// in a service provider's register() method
public function register()
{
$this->app->singleton(\App\Services\OrderCalculator::class, function ($app) {
return new \App\Services\OrderCalculator();
});
}
Registering an alias (optional, for a shorter name)
// config/app.php
'aliases' => Facade::defaultAliases()->merge([
'OrderCalculator' => App\Facades\OrderCalculator::class,
])->toArray(),
This lets you use the bare OrderCalculator::calculateTotal() without the full use App\Facades\OrderCalculator; import in every file — a convenience, not a requirement, since importing the facade class directly (as in the earlier example) works identically without touching this config.
Why facades are convenient, and their real trade-off
Facades give a clean, memorable, static-looking syntax without sacrificing testability (Laravel facades support Facade::shouldReceive() mocking in tests, unlike a genuinely static method call) — but overusing custom facades for every service in an application can obscure the actual dependency graph, since a facade call doesn't show up as a constructor dependency the way normal dependency injection does. Reaching for a facade is most justified for something used pervasively across many unrelated parts of the app, similar to how Laravel's own built-in facades (Cache, Auth, Log) are used.