| name | helpers |
| description | Stateless utility classes providing shared formatting, conversion, or calculation logic needed across multiple parts of the application. |
| compatible_agents | ["architect","implement","refactor","review"] |
Helpers
When to Use
- Utility logic is reused in multiple bounded contexts.
- Logic is stateless and does not need DB, HTTP, or authorization concerns.
- The same formatting/conversion logic appears in resources, views, and domain services.
When Not to Use
- Logic is used once in one method (keep it inline for clarity).
- Logic contains business rules (use Action/Service).
- Logic requires external dependencies or stateful configuration (prefer an injected service).
Preconditions
- Project PHP version supports typed signatures used by helper methods (nullable/union types as needed).
- Target formatter/parser dependencies are installed (for example
nesbot/carbon for date parsing).
- Shared behavior and expected null handling are agreed before extracting to helper.
- Existing call sites are identified so extraction actually removes duplication.
Step-by-Step Helper Creation Checklist
Rules
- Helper classes live in
app/Helpers/
- Naming:
PascalCase with a Helper suffix → StringHelper, MoneyHelper, DateHelper
- Helpers are for utilities needed in multiple places across the project
- Static methods are preferred for pure stateless transformations
- Use instance helpers only when collaborators/config must be injected (locale, formatter strategy, tenant settings)
- As helper count grows, group by namespace (
App\Helpers\Formatting\, App\Helpers\Conversion\)
- Never put business logic in helpers — use Actions or Services
- Never put database queries in helpers — use Model Scopes
- Never put HTTP requests in helpers — use Services
Static vs Instance Guidance
- Use
static when output depends only on input arguments.
- Use instance methods when behavior depends on constructor-provided collaborators/config.
- Do not create instance helpers just to mimic service-container usage when no dependencies are needed.
Examples
namespace App\Helpers;
use Carbon\Carbon;
class DateHelper
{
public static function format(mixed $date, string $format = 'd M Y'): ?string
{
if (is_null($date)) {
return null;
}
return Carbon::parse($date)->format($format);
}
}
namespace App\Helpers;
class MoneyHelper
{
public static function format(int $amountInCents, string $currency = 'CHF'): string
{
return number_format($amountInCents / 100, 2) . ' ' . $currency;
}
}
'created_at' => DateHelper::format($this->created_at),
'amount' => MoneyHelper::format($this->amount_in_cents),
namespace App\Helpers\Formatting;
class LocalizedMoneyFormatter
{
public function __construct(
private readonly string $defaultCurrency = 'CHF',
) {}
public function format(int $amountInCents, ?string $currency = null): string
{
$resolvedCurrency = $currency ?? $this->defaultCurrency;
return number_format($amountInCents / 100, 2) . ' ' . $resolvedCurrency;
}
}
Testing Guidance
use App\Helpers\DateHelper;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;
class DateHelperTest extends TestCase
{
#[Test]
public function it_formats_dates_and_handles_null(): void
{
$this->assertSame('21 Jan 2026', DateHelper::format('2026-01-21', 'd M Y'));
$this->assertNull(DateHelper::format(null));
}
}
Anti-Patterns
- Putting business logic in a helper (belongs in an Action or Service)
- Putting database queries in a helper (use Model scopes instead)
- Using a helper for logic only needed in one place (keep it inline)
- Creating a catch-all
AppHelper or Utils class — keep helpers domain-specific
- Silent null fallback that hides invalid upstream data
References
- Carbon Documentation
- Related:
Actions/SKILL.md — for business logic
- Related:
Resources/SKILL.md — helpers are commonly used for formatting in API resources
Null-Handling Pitfall (Before/After)
public static function format(?string $date, string $format = 'd M Y'): string
{
return Carbon::parse($date)->format($format);
}
public static function format(?string $date, string $format = 'd M Y'): ?string
{
if ($date === null) {
return null;
}
return Carbon::parse($date)->format($format);
}