michael-rubel/laravel-formatters
Laravel Formatters provides a clean, extensible set of formatter classes for common tasks like money, phone numbers, names, dates, and more. Formatters auto-resolve through the container and support method chaining, making consistent formatting simple throughout your app.
composer require michael-rubel/laravel-formatters
The package is small and opinionated: it gives you an abstract MichaelRubel\Formatters\Formatter, a formatter() helper that resolves formatters from the container, a CollectionFormatter base for collection/array transformations, and (typically) a make:formatter generator. There is no service provider wiring you need to touch in a standard Laravel app — package discovery handles it.
First place to look is the source (vendor/michael-rubel/laravel-formatters/src) and the README; the public surface is intentionally tiny, so a five-minute read covers it.
Create your first formatter:
namespace App\Formatters;
use MichaelRubel\Formatters\Formatter;
class PriceFormatter extends Formatter
{
public function format(float|int|null $value): string
{
return $value === null
? '—'
: number_format($value, 2, '.', ' ');
}
}
First real use case — format at the boundary of your app (Blade, API resource, mail), not inside the model:
formatter(PriceFormatter::class)->format($order->total);
One formatter per concern. Keep them tiny and pure: PriceFormatter, DateFormatter, PhoneFormatter, FullNameFormatter. A formatter that touches the database or dispatches events has stopped being a formatter.
Configuration via the constructor. Pass format strings, locales, or currency symbols as constructor arguments and bind context-specific variants in a service provider:
$this->app->bind(EuPriceFormatter::class, fn () => new PriceFormatter(currency: 'EUR'));
Compose formatters. A UserPresenter-style formatter can depend on smaller formatters through constructor injection, so formatting rules stay in one place per concept:
class UserCardFormatter extends Formatter
{
public function __construct(private DateFormatter $dates) {}
public function format(User $user): array
{
return [
'name' => $user->name,
'joined_at' => $this->dates->format($user->created_at),
];
}
}
Use CollectionFormatter for lists. Mapping a collection to presentation data is the common second use case; extending CollectionFormatter keeps the map/filter logic reusable across controllers and resources.
Keep it at the edge. Call formatters from Blade ({{ formatter(PriceFormatter::class)->format($order->total) }}), API resources, or mailables — never from model accessors that might later be used for persistence, and never from formatters you then feed back into update().
Inject, don't app(). Resolve a formatter once in a controller/service and reuse it, rather than calling the helper in a loop. For expensive formatters (money libraries, locale data), register them as singletons.
Test them like functions. Because formatters are plain classes, unit tests need no Laravel bootstrapping beyond the container: instantiate directly or resolve through app(), assert exact output, and use Carbon::setTestNow() for anything date-related. This is where the pattern pays off versus scattered number_format()/->format() calls.
null?" — an em dash, an empty string, or null. Inconsistent choices leak into Blade and API responses and are painful to normalize later.number_format, Carbon::format, and trans() all depend on ambient configuration. Pass locale/timezone explicitly (or pin them in constructor args) so tests and queued jobs don't drift from the request context.{!! !!} deliberately, or return plain text and let Blade escape it.app() call will fail unless the parameters are auto-resolvable or explicitly bound. Bind contextual variants in a service provider rather than passing raw arguments everywhere.null relations, or a decimal stored as a string.make:formatter stub path/namespace, publish it only if you want non-default locations; otherwise let the default App\Formatters convention stand.Formatter, override format(), add your own abstract base with shared helpers, and bind interfaces for test doubles. There are no macros or magic hooks to learn.composer.json and keep a one-class fallback if you need long-term stability.How can I help you explore Laravel packages today?