A Laravel (not only) package to track and manage progress for different tasks or processes.
Install the package via Composer:
composer require verseles/progressableOptionally, you can publish the config file with:
php artisan vendor:publish --provider="Verseles\Progressable\ProgressableServiceProvider" --tag="config"The published config file provides the following options:
return [
'ttl' => env('PROGRESSABLE_TTL', 1140), // Cache TTL in minutes (default: 19 hours)
'prefix' => env('PROGRESSABLE_PREFIX', 'progressable'), // Cache key prefix
'precision' => env('PROGRESSABLE_PRECISION', 2), // Default decimal precision
];This package provides a main trait: Progressable.
The Progressable trait can be used in any class that needs to track progress.
"Local" refers to the progress of your class/model/etc, while "Overall" represents the average of all Progressable instances using the same unique name.
For parallel jobs, use one stable overall key for the parent operation and one stable local key per worker. The default cache-backed store updates each local entry independently under a cache lock when the cache driver supports atomic locks. Under heavy contention, Laravel may throw Illuminate\Contracts\Cache\LockTimeoutException if the lock cannot be acquired before the wait timeout.
$progress->setOverallUniqueName("routine-run:{$runId}");
$progress->setLocalKey("module:{$moduleId}");
$progress->setLocalProgress(42);Custom setCustomSaveData() / setCustomGetData() callbacks still receive the full progress snapshot, so custom storage should implement its own concurrency control for parallel writers.
use Verseles\Progressable\Progressable;
class MyFirstTask
{
use Progressable;
public function __construct()
{
$this->setOverallUniqueName('my-job')->resetOverallProgress();
}
public function run()
{
foreach (range(1, 100) as $value) {
$this->setLocalProgress($value);
echo "Overall Progress: " . $this->getOverallProgress() . "%" . PHP_EOL;
}
}
}| Method | Description |
|---|---|
setOverallUniqueName(string $name) |
Set the progress group identifier |
setLocalProgress(float $progress) |
Set progress for this instance (0-100) |
getLocalProgress(?int $precision) |
Get current progress (default precision from config) |
incrementLocalProgress(float $amount = 1) |
Increment progress by amount (can be negative) |
resetLocalProgress() |
Reset instance progress to 0 |
getOverallProgress(?int $precision) |
Get average progress of all instances |
resetOverallProgress() |
Clear all progress data for the group |
removeLocalFromOverall() |
Remove this instance from overall calculation |
getEstimatedTimeRemaining() |
Get estimated time remaining in seconds for this instance |
getOverallEstimatedTimeRemaining() |
Get estimated time remaining in seconds for the overall progress |
| Method | Description |
|---|---|
isComplete() |
Check if local progress is 100% |
isOverallComplete() |
Check if overall progress is 100% |
| Method | Description |
|---|---|
setStatusMessage(?string $message) |
Set a status message for this instance |
getStatusMessage() |
Get the current status message |
setMetadata(array $metadata) |
Set metadata array for this instance |
getMetadata() |
Get all metadata |
addMetadata(string $key, mixed $value) |
Add/update a single metadata value |
getMetadataValue(string $key, mixed $default) |
Get a single metadata value |
| Method | Description |
|---|---|
onProgressChange(callable $callback) |
Called when progress changes: fn($new, $old, $instance) |
onComplete(callable $callback) |
Called when progress reaches 100%: fn($instance) |
| Method | Description |
|---|---|
setTTL(int $minutes) |
Set cache time-to-live |
getTTL() |
Get current TTL |
setPrecision(int $decimals) |
Set decimal precision for progress values |
getPrecision() |
Get current precision |
setPrefixStorageKey(string $prefix) |
Set custom cache key prefix (before setting unique name) |
setLocalKey(string $key) |
Set custom identifier for this instance |
getLocalKey() |
Get current instance identifier |
| Method | Description |
|---|---|
setCustomSaveData(callable $callback) |
Set custom save callback |
setCustomGetData(callable $callback) |
Set custom get callback |
use Verseles\Progressable\Progressable;
class FileProcessor
{
use Progressable;
public function process(array $files)
{
$this->setOverallUniqueName('file-processing')
->resetOverallProgress()
->onProgressChange(fn($new, $old) => logger("Progress: {$old}% -> {$new}%"))
->onComplete(fn() => logger("All files processed!"));
$increment = 100 / count($files);
foreach ($files as $index => $file) {
$this->setStatusMessage("Processing: {$file}")
->addMetadata('current_file', $file)
->addMetadata('files_processed', $index + 1);
$this->processFile($file);
$this->incrementLocalProgress($increment);
}
}
}You can use the Progressable trait without Laravel by providing custom save and get data callbacks:
use Verseles\Progressable\Progressable;
$storage = [];
$saveCallback = function ($key, $data, $ttl) use (&$storage) {
$storage[$key] = $data;
};
$getCallback = function ($key) use (&$storage) {
return $storage[$key] ?? [];
};
$obj = new class { use Progressable; };
$obj
->setCustomSaveData($saveCallback)
->setCustomGetData($getCallback)
->setOverallUniqueName('my-task')
->resetOverallProgress()
->setLocalProgress(25);
echo $obj->getLocalProgress(); // 25# Run tests
composer test
# Check code style
composer lint:test
# Fix code style
composer lint
# Run static analysis
composer analysePlease see CONTRIBUTING.md for details.
Please see CHANGELOG.md for recent changes.
The Progressable package is open-sourced software licensed under the MIT license.