symfony/stopwatch
Symfony Stopwatch is a lightweight profiling utility to measure execution time and memory usage in PHP. Start/stop named events, record laps, and group timings into sections (phases) to benchmark code paths and understand performance bottlenecks.
Install via Composer:
composer require symfony/stopwatch
use Symfony\Component\Stopwatch\Stopwatch;
public function index(Stopwatch $stopwatch)
{
$stopwatch->start('controller_execution');
// Your controller logic here
$data = $this->fetchData();
$event = $stopwatch->stop('controller_execution');
// Log or display results
\Log::info('Controller took ' . $event->getDuration() . 'ms');
return view('home', compact('data'));
}
start()/stop() for granular profiling.openSection('database')).lap('query_execution')).Stopwatch via service container.Stopwatch integration in Illuminate\Support\Facades\Stopwatch.public function handle($request, Closure $next, Stopwatch $stopwatch)
{
$stopwatch->start('middleware_execution');
$response = $next($request);
$event = $stopwatch->stop('middleware_execution');
\Log::debug('Middleware took ' . $event->getDuration() . 'ms');
return $response;
}
public function fetchUserData(Stopwatch $stopwatch)
{
$stopwatch->start('fetch_user_data');
// Business logic
$user = User::find($id);
$stopwatch->lap('database_query');
$data = $this->processUserData($user);
$event = $stopwatch->stop('fetch_user_data');
\Log::info('User data fetch: ' . $event->getDuration() . 'ms');
return $data;
}
protected function execute(InputInterface $input, OutputInterface $output, Stopwatch $stopwatch)
{
$stopwatch->start('command_execution');
// Command logic
$this->processFiles();
$event = $stopwatch->stop('command_execution');
$output->writeln(sprintf('Command took %dms', $event->getDuration()));
}
public function getSlowQueries(Stopwatch $stopwatch)
{
$stopwatch->start('query_execution');
$results = DB::select('SELECT * FROM large_table');
$event = $stopwatch->stop('query_execution');
\Log::warning('Query took ' . $event->getDuration() . 'ms');
return $results;
}
Service Provider Binding:
public function register()
{
$this->app->singleton(Stopwatch::class, function () {
return new Stopwatch();
});
}
Global Profiling Middleware:
$router->middleware('profile', function ($request, $next, Stopwatch $stopwatch) {
$stopwatch->start('request_' . $request->id());
$response = $next($request);
$event = $stopwatch->stop('request_' . $request->id());
\Log::debug('Request ' . $request->id() . ' took ' . $event->getDuration() . 'ms');
return $response;
});
Event Listeners:
public function handle(Event $event, Stopwatch $stopwatch)
{
$stopwatch->start('event_' . get_class($event));
// Handle event
$stopwatch->stop('event_' . get_class($event));
}
Contextual Data:
$event = $stopwatch->stop('event_name');
$event->setData(['user_id' => auth()->id(), 'query' => $query]);
Section-Based Profiling:
$stopwatch->openSection('database');
$stopwatch->start('query_1');
// ...
$stopwatch->stop('query_1');
$stopwatch->start('query_2');
// ...
$stopwatch->stop('query_2');
$stopwatch->stopSection('database');
Custom Event Formatting:
$event = $stopwatch->stop('event_name');
$formatted = sprintf(
'Event: %s | Duration: %dms | Memory: %dKB',
$event->getName(),
$event->getDuration(),
$event->getMemory() / 1024
);
Memory Leaks:
$stopwatch->stop('event_name'); // Explicitly stop to free memory
Nested Sections:
openSection()) as it can complicate event hierarchy and increase overhead.Event Naming Collisions:
query_user_1, query_user_2).Lap Timing Quirks:
lap()) measure time since the last lap or start, not the event start. Reset with start() if needed.PHP 8.1+ Requirement:
composer require symfony/stopwatch:^7.4
Stopwatch Overhead:
Inspect Events:
$event = $stopwatch->stop('event_name');
dump($event->getDuration(), $event->getMemory(), $event->getData());
Check for Active Events:
stop() matches start() calls. Use:
$stopwatch->getEvents(); // List all active events
Memory Profiling:
getMemory()) are approximate. For precise memory profiling, use Xdebug.Section Validation:
$stopwatch->stopSection('section_name'); // Must match openSection()
Custom Event Classes:
Extend Symfony\Component\Stopwatch\StopwatchEvent to add metadata:
class CustomEvent extends StopwatchEvent
{
public function setCustomData(array $data): self
{
$this->data['custom'] = $data;
return $this;
}
}
Stopwatch Decorator: Wrap Stopwatch to add logic (e.g., auto-logging):
class LoggingStopwatch implements StopwatchInterface
{
private $decorated;
public function __construct(Stopwatch $stopwatch)
{
$this->decorated = $stopwatch;
}
public function start($name, array $data = [])
{
$this->decorated->start($name, $data);
\Log::debug("Started profiling: {$name}");
}
// Delegate other methods...
}
Laravel Service Provider Integration:
public function boot()
{
$this->app->resolving(Stopwatch::class, function ($stopwatch) {
$stopwatch->register('laravel', function () {
return new LaravelStopwatchEvent();
});
});
}
Time Precision:
microtime(true) for timing. For higher precision, consider custom implementations.Memory Measurement:
memory_get_usage(). For accurate deltas, measure before/after:
$startMemory = memory_get_usage();
// ... code ...
$endMemory = memory_get_usage();
$memoryUsed = $endMemory - $startMemory;
Event Lifecycle:
setData() before stopping.Thread Safety:
Disable in Production:
if (app()->environment('production')) {
$stopwatch = new Stopwatch(false); // Disable
}
Batch Events: For high-frequency events (e
How can I help you explore Laravel packages today?