📖 2 minutes read
When building systems with both user interfaces and internal logic, explicitly separating display names from system identifiers prevents cascading changes when marketing decides to rename a feature.
The pattern: use stable identifiers (type, id, slug) for code/database/filenames, and store display names (name, label) separately in configuration:
// config/features.php
return [
'available_reports' => [
[
'type' => 'sales_report', // Stable system identifier
'name' => 'Quarterly Sales Analysis', // User-facing display name
'permission' => 'view_sales_reports',
],
[
'type' => 'inventory_report',
'name' => 'Stock Level Summary',
'permission' => 'view_inventory',
],
],
];
In your controllers:
class ReportController
{
public function index()
{
$reports = collect(config('features.available_reports'))
->filter(fn ($report) => auth()->user()->can($report['permission'] ?? ''))
->map(fn ($report) => [
'id' => $report['type'], // Internal ID for API/routes
'label' => $report['name'], // Display name for UI
]);
return view('reports.index', compact('reports'));
}
public function generate(string $reportType)
{
// Use 'type' for routing, job dispatch, filename generation
$job = match ($reportType) {
'sales_report' => new GenerateSalesReport(),
'inventory_report' => new GenerateInventoryReport(),
default => throw new InvalidArgumentException(),
};
dispatch($job);
}
}
Job classes use the stable identifier:
class GenerateSalesReport implements ShouldQueue
{
public function handle()
{
$filename = 'sales_report_' . now()->format('Y-m-d') . '.pdf';
Storage::put("exports/{$filename}", $this->generatePdf());
// Filename: 'sales_report_2024-03-05.pdf' — never changes
}
}
Why this matters:
- Marketing freedom: “Quarterly Sales Analysis” can become “Revenue Insights Dashboard” without touching code
- Stability: Database queries, API endpoints, and filenames don’t break when display names change
- A/B testing: Easily test different labels for the same feature
- Internationalization: Display names can be translated while system identifiers stay English
What NOT to do:
// ❌ DON'T couple display names to class constants
class GenerateSalesReport
{
public const DISPLAY_NAME = 'Quarterly Sales Analysis';
public function getFilename()
{
return self::DISPLAY_NAME . '_' . now()->format('Y-m-d') . '.pdf';
// Filename: 'Quarterly Sales Analysis_2024-03-05.pdf' — spaces, changes when label changes
}
}
// ❌ DON'T hardcode display names in multiple places
// Controller
$reportName = 'Quarterly Sales Analysis';
// Blade view
Quarterly Sales Analysis
// Email notification
Mail::send(..., ['report' => 'Quarterly Sales Analysis']);
// Now you have 3+ places to update when marketing changes the name
The right approach:
- Store display names in
config/*.phpor database tables where non-developers can update them - Use system identifiers everywhere in code (
sales_report, not"Quarterly Sales Analysis") - Fetch display names at runtime from the centralized source
Your codebase becomes more flexible, and non-technical stakeholders can update user-facing labels without opening a pull request.
Leave a Reply