Installation:
composer require dtl/time-distance-bundle
Add to config/bundles.php:
Dtl\TimeDistanceBundle\DtlTimeDistanceBundle::class => ['all' => true],
Publish config (optional):
php artisan config:publish dtl/time-distance-bundle
First Use Case: Display a formatted distance in a Twig template:
{# Convert 5000 meters to miles #}
{{ 5000|format_distance('miles') }}
Output: 3.106856 (miles).
Distance Conversion in Views:
Use format_distance for dynamic unit switching (e.g., toggle between km/miles in a profile page):
{% set distance = user.distance_ran|format_distance(user.preferred_unit) %}
Stopwatch Formatting: Convert timestamps (e.g., from a race) to stopwatch format:
{{ race.duration_seconds|seconds_to_stopwatch }}
Use case: Leaderboards, race results, or user activity logs.
Pace Calculations: Display athlete performance metrics:
{{ athlete.total_time|average_pace(athlete.distance, 'km') }}
Use case: Training dashboards, progress tracking.
Form Integration: Bind distance inputs to normalized units (meters) in forms:
// In a controller
$form = $this->createForm(DistanceType::class, $user);
{{ form_row(form.distance) }} {# Renders as <input type="number"> #}
Tip: Validate normalized values in the controller (e.g., >= 0).
Dynamic Unit Selection:
Store user preferences in the database (e.g., preferred_distance_unit) and pass to Twig:
{% set unit = user.preferred_distance_unit ?? 'km' %}
{{ distance|format_distance(unit) }}
API Responses: Return normalized values (meters/seconds) in JSON APIs, then format client-side or in Twig. Example:
{ "distance": 5000, "unit": "m" }
{{ data.distance|format_distance(data.unit) }}
Localization:
Extend the bundle’s DistanceConverter service to support custom units (e.g., nautical miles) by overriding the getConversionFactor method.
Testing:
Mock the DistanceConverter and StopwatchFormatter services in PHPUnit:
$this->app->instance(DistanceConverter::class, $mockConverter);
Unit Mismatch:
format_distance will yield incorrect results.{{ 5|format_distance('km') }} {# Incorrect: 5 km → 5000 m → 3.106856 miles #}
{{ 5000|format_distance('miles') }} {# Correct #}
Stopwatch Overflow:
seconds_to_stopwatch does not handle values > 24 hours gracefully (e.g., 604800 seconds = 1 week → 168:00:00).Form Type Quirks:
DistanceType and StopwatchType form fields do not enforce normalization. Validate manually:
$distance = $form->get('distance')->getData();
if ($distance < 0) {
$form->addError(...);
}
Precision Handling:
format_distance defaults to 6 decimal places. Override via the second argument:
{{ 1000|format_distance('miles', 2) }} {# Output: 0.62 #}
Check Config:
Verify normalized_distance_unit in config/dtl_time_distance.php matches your data model (default: m).
// config/dtl_time_distance.php
return [
'normalized_distance_unit' => 'm', // or 'ft', 'mi', etc.
];
Twig Filter Availability:
Ensure the bundle is registered in bundles.php and Twig is auto-reloaded:
php artisan config:clear
php artisan cache:clear
Custom Units:
Extend the DistanceConverter service by binding a decorator:
// src/Service/DistanceConverterDecorator.php
class DistanceConverterDecorator extends DistanceConverter {
public function getConversionFactor($unit) {
$factor = parent::getConversionFactor($unit);
return $unit === 'nautical_miles' ? $factor * 1.15078 : $factor;
}
}
Register in services.yaml:
Dtl\TimeDistanceBundle\Service\DistanceConverter: '@App\Service\DistanceConverterDecorator'
Stopwatch Formatting:
Override the StopwatchFormatter to customize separators or add milliseconds:
// src/Twig/StopwatchExtension.php
class StopwatchExtension extends \Dtl\TimeDistanceBundle\Twig\StopwatchExtension {
public function getStopwatchFormat(int $seconds): string {
// Custom logic (e.g., add milliseconds)
return sprintf('%02d:%02d:%02d.%03d',
floor($seconds / 3600),
($seconds % 3600) / 60,
$seconds % 60,
($seconds * 1000) % 1000
);
}
}
Bind the extension in services.yaml:
twig.extension.stopwatch: '@App\Twig\StopwatchExtension'
Form Type Extensions:
Add attributes or constraints to DistanceType/StopwatchType by extending the class and overriding configureOptions or buildForm.
How can I help you explore Laravel packages today?