├── .github └── workflows │ └── phpunit.yml ├── .gitignore ├── LICENSE.md ├── README.md ├── composer.json ├── composer.lock ├── config └── progressable.php ├── makefile ├── merge.bash ├── src ├── Exceptions │ ├── UniqueNameAlreadySetException.php │ └── UniqueNameNotSetException.php ├── Progressable.php └── ProgressableServiceProvider.php └── tests └── ProgressableTest.php /.github/workflows/phpunit.yml: -------------------------------------------------------------------------------- 1 | name : PHPUnit Test 2 | 3 | on : 4 | push : 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs : 13 | test: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps : 18 | - 19 | name: Set up PHP 20 | uses: shivammathur/setup-php@v2 21 | with: 22 | php-version: '8.4' 23 | 24 | - 25 | uses: actions/checkout@v4 26 | 27 | - 28 | name: Validate composer.json and composer.lock 29 | run : composer validate --strict 30 | 31 | - 32 | name: Cache Composer packages 33 | id : composer-cache 34 | uses: actions/cache@v4 35 | with: 36 | path : vendor 37 | key : ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} 38 | restore-keys: | 39 | ${{ runner.os }}-php- 40 | 41 | - 42 | name: Install dependencies 43 | run : composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist 44 | 45 | - 46 | name: Run test suite 47 | run : composer run-script test 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | .env* 3 | .idea/ 4 | .vscode/ 5 | node_modules/ 6 | .phpunit.result.cache 7 | *.log 8 | .DS_Store 9 | ai.txt 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2024 Hélio Oliveira 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Progressable 🚀 2 | 3 | A Laravel [(not only)](#without-laravel) package to track and manage progress for different tasks or processes. 4 | 5 | ![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/verseles/progressable/phpunit.yml?style=for-the-badge&label=PHPUnit) 6 | 7 | ## Installation 8 | 9 | Install the package via Composer: 10 | 11 | ```bash 12 | composer require verseles/progressable 13 | ``` 14 | 15 | Optionally, you can publish the config file with: 16 | 17 | ```bash 18 | php artisan vendor:publish --provider="Verseles\Progressable\ProgressableServiceProvider" --tag="config" 19 | ``` 20 | 21 | ## Configuration 22 | 23 | The published config file provides the following options: 24 | 25 | ```php 26 | return [ 27 | 'ttl' => env('PROGRESSABLE_TTL', 1140), // Default cache time-to-live (in minutes) 28 | 29 | 'prefix' => env('PROGRESSABLE_PREFIX', 'progressable'), // Cache key prefix 30 | ]; 31 | ``` 32 | 33 | ## Usage 34 | 35 | This package provides a main trait: `Progressable`. 36 | 37 | ### With Laravel 38 | 39 | The `Progressable` trait can be used in any class that needs to track progress. It provides two main methods: `updateLocalProgress` and `getLocalProgress`. 40 | 41 | "Local" refers to the progress of your class/model/etc, while "Overall" represents the sum of all Progressable classes using the same key name. 42 | 43 | ### Example 44 | ```php 45 | use Verseles\Progressable; 46 | 47 | class MyFirstTask 48 | { 49 | use Progressable; 50 | 51 | public function __construct() 52 | { 53 | $this->setOverallUniqueName('my-job')->resetOverallProgress(); 54 | } 55 | 56 | public function run() 57 | { 58 | foreach (range(1, 100) as $value) { 59 | $this->setLocalProgress($value); 60 | usleep(100000); // Sleep for 100 milliseconds 61 | echo "Overall Progress: " . $this->getOverallProgress() . "%" . PHP_EOL; 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | ```php 68 | use Verseles\Progressable; 69 | 70 | class MySecondTask 71 | { 72 | use Progressable; 73 | 74 | public function __construct() 75 | { 76 | $this->setOverallUniqueName('my-job'); 77 | } 78 | 79 | public function run() 80 | { 81 | foreach (range(1, 100) as $value) { 82 | $this->setLocalProgress($value); 83 | usleep(100000); // Sleep for 100 milliseconds 84 | echo "Overall Progress: " . $this->getOverallProgress() . "%" . PHP_EOL; 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | - Use `setOverallUniqueName` to associate the progress with a specific overall progress instance. 91 | - `setLocalProgress` updates the progress for the current instance. 92 | - `getLocalProgress` retrieves the current progress. 93 | - `getOverallProgress` retrieves the overall progress data. 94 | - `resetOverallProgress` resets the overall progress (recommended after setting the unique name for the first time). 95 | 96 | The progress value ranges from 0 to 100. 97 | 98 | ### Without Laravel 99 | 100 | You can use the `Progressable` trait without Laravel by providing custom save and get data methods. 101 | 102 | ### Example 103 | 104 | ```php 105 | $overallUniqueName = 'test-without-laravel'; 106 | 107 | $my_super_storage = []; 108 | 109 | $saveCallback = function ($key, $data, $ttl) use (&$my_super_storage) { 110 | $my_super_storage[$key] = $data; 111 | }; 112 | 113 | $getCallback = function ($key) use (&$my_super_storage) { 114 | return $my_super_storage[$key] ?? []; 115 | }; 116 | 117 | 118 | $obj1 = new class { use Progressable; }; 119 | $obj1 120 | ->setCustomSaveData($saveCallback) 121 | ->setCustomGetData($getCallback) 122 | ->setOverallUniqueName($overallUniqueName) 123 | ->resetOverallProgress() 124 | ->updateLocalProgress(25); 125 | 126 | $obj2 = new class { use Progressable; }; 127 | $obj2 128 | ->setCustomSaveData($saveCallback) 129 | ->setCustomGetData($getCallback) 130 | ->setOverallUniqueName($overallUniqueName) 131 | ->updateLocalProgress(75); 132 | 133 | ``` 134 | 135 | ## Testing 136 | 137 | To run the tests, execute the following command: 138 | 139 | ```bash 140 | make 141 | ``` 142 | 143 | ## License 144 | 145 | The Progressable package is open-sourced software licensed under the [MIT license](./LICENSE.md). 146 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "verseles/progressable", 3 | "description": "A Laravel (not only) package to track and manage progress for different tasks or processes.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Hélio Oliveira", 9 | "email": "open@helio.me" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=8.4", 14 | "illuminate/support": "^v11|^v12", 15 | "illuminate/cache": "^11|^v12", 16 | "illuminate/contracts": "^11|^v12" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Verseles\\Progressable\\": "src/" 21 | } 22 | }, 23 | "autoload-dev": { 24 | "psr-4": { 25 | "Verseles\\Progressable\\Tests\\": "tests/" 26 | } 27 | }, 28 | "extra": { 29 | "laravel": { 30 | "providers": [ 31 | "Verseles\\Progressable\\ProgressableServiceProvider" 32 | ] 33 | } 34 | }, 35 | "scripts": { 36 | "test": "vendor/bin/phpunit tests/ProgressableTest.php --testdox" 37 | }, 38 | "config": { 39 | "sort-packages": true 40 | }, 41 | "minimum-stability": "dev", 42 | "prefer-stable": true, 43 | "require-dev": { 44 | "orchestra/testbench": "^9.0", 45 | "phpunit/phpunit": "^12" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /config/progressable.php: -------------------------------------------------------------------------------- 1 | env('PROGRESSABLE_TTL', 1140), 16 | 17 | /* 18 | |-------------------------------------------------------------------------- 19 | | Cache Prefix 20 | |-------------------------------------------------------------------------- 21 | | 22 | | This option specifies the default prefix for the progress data. 23 | | 24 | */ 25 | 26 | 'prefix' => env('PROGRESSABLE_PREFIX', 'progressable'), 27 | ]; 28 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | composer test 4 | 5 | ai: 6 | ./merge.bash \ 7 | --output=ai.txt \ 8 | --folder-recursive="." \ 9 | --ignore-folders=vendor \ 10 | --ignore-extensions=lock,bash \ 11 | --ignore-files=LICENSE.md \ 12 | --ignore-files=makefile 13 | -------------------------------------------------------------------------------- /merge.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Part of the Progressable package 4 | # https://github.com/verseles/progressable 5 | 6 | # Function to display help 7 | function show_help { 8 | echo "Usage: $0 [options]" 9 | echo "Options:" 10 | echo " --folder=\"path\" Specifies a folder to merge files (can be used multiple times)" 11 | echo " --folder-recursive=\"path\" Specifies a folder to merge files recursively (can be used multiple times)" 12 | echo " --output=\"file name\" Specifies the output file name (default: output.txt)" 13 | echo " --ignore-extensions=\"extensions\" Specifies file extensions to ignore (comma separated)" 14 | echo " --ignore-folders=\"folders\" Specifies folders to globally ignore (can be used multiple times)" 15 | echo " --ignore-files=\"files\" Specifies files to globally ignore (can be used multiple times)" 16 | echo "" 17 | echo "Examples:" 18 | echo " $0 --folder=/path/folder1 --folder=/path/folder2" 19 | echo " $0 --folder-recursive=/path/folder --output=result.txt" 20 | echo " $0 --folder=/path/folder1 --folder-recursive=/path/folder2 --ignore-extensions=txt,log --ignore-files=file1,file2" 21 | } 22 | 23 | # Variables to store parameters 24 | folders=() 25 | recursive_folders=() 26 | output_file="output.txt" 27 | ignore_extensions="" 28 | ignore_folders=() 29 | ignore_files=() 30 | 31 | # Process parameters 32 | while [[ $# -gt 0 ]]; do 33 | case "$1" in 34 | --folder=*) 35 | folders+=("${1#*=}") 36 | shift 37 | ;; 38 | --folder-recursive=*) 39 | recursive_folders+=("${1#*=}") 40 | shift 41 | ;; 42 | --output=*) 43 | output_file="${1#*=}" 44 | shift 45 | ;; 46 | --ignore-extensions=*) 47 | ignore_extensions="${1#*=}" 48 | shift 49 | ;; 50 | --ignore-folders=*) 51 | ignore_folders+=("${1#*=}") 52 | shift 53 | ;; 54 | --ignore-files=*) 55 | ignore_files+=("${1#*=}") 56 | shift 57 | ;; 58 | *) 59 | echo "Invalid option: $1" 60 | show_help 61 | exit 1 62 | ;; 63 | esac 64 | done 65 | 66 | # Clear output file otherwise it will append 67 | truncate -s 0 "$output_file" 68 | 69 | # Verify if no parameter was provided 70 | if [[ ${#folders[@]} -eq 0 && ${#recursive_folders[@]} -eq 0 ]]; then 71 | show_help 72 | exit 0 73 | fi 74 | 75 | # Function to process a folder 76 | function process_folder { 77 | local folder="$1" 78 | for file in "$folder"/*; do 79 | if [[ -f "$file" ]]; then 80 | local filename="${file##*/}" 81 | if [[ "$file" != "$output_file" && "$folder/$output_file" != "$file" ]]; then 82 | if [[ " ${ignore_files[*]} " != *" $filename "* ]]; then 83 | local extension="${file##*.}" 84 | if [[ "$ignore_extensions" != *"$extension"* ]]; then 85 | ignore_match=false 86 | for ignored_folder in ${ignore_folders[*]}; do 87 | if [[ "${file}/" =~ ${ignored_folder} ]]; then 88 | ignore_match=true 89 | break 90 | fi 91 | done 92 | 93 | if [[ ${ignore_match} == false ]]; then 94 | echo "Adding file: $file" 95 | echo "FILE: $file" >> "$output_file" 96 | cat "$file" >> "$output_file" 97 | echo "" >> "$output_file" 98 | fi 99 | fi 100 | fi 101 | fi 102 | fi 103 | done 104 | } 105 | 106 | # Function to process a folder recursively 107 | function process_folder_recursive { 108 | local folder="$1" 109 | for file in "$folder"/*; do 110 | if [[ -f "$file" ]]; then 111 | local filename="${file##*/}" 112 | if [[ "$file" != "$output_file" && "$folder/$output_file" != "$file" ]]; then 113 | if [[ " ${ignore_files[*]} " != *" $filename "* ]]; then 114 | local extension="${file##*.}" 115 | if [[ "$ignore_extensions" != *"$extension"* ]]; then 116 | ignore_match=false 117 | for ignored_folder in ${ignore_folders[*]}; do 118 | if [[ "${file}/" =~ ${ignored_folder} ]]; then 119 | ignore_match=true 120 | break 121 | fi 122 | done 123 | 124 | if [[ ${ignore_match} == false ]]; then 125 | echo "Adding file recursively: $file" 126 | echo "FILE: $file" >> "$output_file" 127 | cat "$file" >> "$output_file" 128 | echo "" >> "$output_file" 129 | fi 130 | fi 131 | fi 132 | fi 133 | elif [[ -d "$file" ]]; then 134 | process_folder_recursive "$file" # Recursively call the function for subfolders 135 | fi 136 | done 137 | } 138 | 139 | # Process specified folders 140 | for folder in "${folders[@]}"; do 141 | process_folder "$folder" 142 | done 143 | 144 | # Process specified folders recursively 145 | for folder in "${recursive_folders[@]}"; do 146 | process_folder_recursive "$folder" 147 | done 148 | 149 | echo "Output file: $output_file" 150 | -------------------------------------------------------------------------------- /src/Exceptions/UniqueNameAlreadySetException.php: -------------------------------------------------------------------------------- 1 | customSaveData = $callback; 68 | return $this; 69 | } 70 | 71 | /** 72 | * Set the callback function for retrieving cache data. 73 | * 74 | * @param callable $callback 75 | * @return $this 76 | */ 77 | public function setCustomGetData(callable $callback): static 78 | { 79 | $this->customGetData = $callback; 80 | return $this; 81 | } 82 | 83 | /** 84 | * Get the overall progress for the unique name. 85 | * 86 | * @param int $precision The precision of the overall progress 87 | * @return float 88 | */ 89 | public function getOverallProgress(int $precision = 2): float 90 | { 91 | $progressData = $this->getOverallProgressData(); 92 | 93 | $totalProgress = array_sum(array_column($progressData, "progress")); 94 | $totalCount = count($progressData); 95 | 96 | if ($totalCount === 0) { 97 | return 0; 98 | } 99 | 100 | return round($totalProgress / $totalCount, $precision, PHP_ROUND_HALF_ODD); 101 | } 102 | 103 | /** 104 | * Get the overall progress data from the storage. 105 | * 106 | * @return array 107 | */ 108 | public function getOverallProgressData(): array 109 | { 110 | if ($this->customGetData !== null) { 111 | return call_user_func($this->customGetData, $this->getStorageKeyName()); 112 | } 113 | 114 | return Cache::get($this->getStorageKeyName(), []); 115 | } 116 | 117 | /** 118 | * Get the cache key for the unique name. 119 | * 120 | * @return string 121 | */ 122 | protected function getStorageKeyName(): string 123 | { 124 | return $this->getPrefixStorageKey() . '_' . $this->getOverallUniqueName(); 125 | } 126 | 127 | /** 128 | * Retrieve the prefix storage key for the PHP function. 129 | * 130 | * @return string 131 | */ 132 | protected function getPrefixStorageKey(): string 133 | { 134 | return $this->customPrefixStorageKey ?? config("progressable.prefix", $this->defaultPrefixStorageKey); 135 | } 136 | 137 | /** 138 | * Get the overall unique name. 139 | * 140 | * @return string the overall unique name 141 | * @throws UniqueNameNotSetException If the overall unique name is not set 142 | */ 143 | public function getOverallUniqueName() 144 | { 145 | if (!isset($this->overallUniqueName) || empty($this->overallUniqueName)) { 146 | throw new UniqueNameNotSetException(); 147 | } 148 | 149 | return $this->overallUniqueName; 150 | } 151 | 152 | /** 153 | * Set the unique name of the overall progress. 154 | * 155 | * @param string $overallUniqueName 156 | * @return $this 157 | */ 158 | public function setOverallUniqueName(string $overallUniqueName): static 159 | { 160 | $this->overallUniqueName = $overallUniqueName; 161 | 162 | $this->makeSureLocalIsPartOfTheCalc(); 163 | 164 | return $this; 165 | } 166 | 167 | /** 168 | * @return void 169 | * @throws UniqueNameNotSetException 170 | */ 171 | public function makeSureLocalIsPartOfTheCalc(): void 172 | { 173 | if ($this->getLocalProgress(0) == 0) { 174 | // This make sure that the class who called this method will be part of the overall progress calculation 175 | $this->resetLocalProgress(); 176 | } 177 | } 178 | 179 | /** 180 | * Get the progress value for this instance 181 | * 182 | * @param int $precision The precision of the local progress 183 | * @return float 184 | */ 185 | public function getLocalProgress(int $precision = 2): float 186 | { 187 | return round($this->progress, $precision, PHP_ROUND_HALF_ODD); 188 | } 189 | 190 | /** 191 | * @return static 192 | * @throws UniqueNameNotSetException 193 | */ 194 | public function resetLocalProgress(): static 195 | { 196 | return $this->setLocalProgress(0); 197 | } 198 | 199 | /** 200 | * Update the progress value for this instance. 201 | * 202 | * @param float $progress 203 | * @return $this 204 | * @throws UniqueNameNotSetException 205 | */ 206 | public function setLocalProgress(float $progress): static 207 | { 208 | $this->progress = max(0, min(100, $progress)); 209 | 210 | return $this->updateLocalProgressData($this->progress); 211 | } 212 | 213 | /** 214 | * Update the progress data in storage. 215 | * 216 | * @return static 217 | */ 218 | protected function updateLocalProgressData(float $progress): static 219 | { 220 | $progressData = $this->getOverallProgressData(); 221 | 222 | $progressData[$this->getLocalKey()] = [ 223 | "progress" => $progress, 224 | ]; 225 | 226 | return $this->saveOverallProgressData($progressData); 227 | } 228 | 229 | /** 230 | * Get the cache identifier for this instance. 231 | * 232 | * @return string 233 | */ 234 | public function getLocalKey(): string 235 | { 236 | return $this->localKey ?? get_class($this) . "@" . spl_object_hash($this); 237 | } 238 | 239 | /** 240 | * Set the local key 241 | * 242 | * @param string $name 243 | * @return $this 244 | */ 245 | public function setLocalKey(string $name): static 246 | { 247 | if (isset($this->getOverallProgressData()[$this->getLocalKey()])) { 248 | // Rename the local key preserving the data 249 | $overallProgressData = $this->getOverallProgressData(); 250 | $overallProgressData[$name] = $overallProgressData[$this->getLocalKey()]; 251 | unset($overallProgressData[$this->getLocalKey()]); 252 | $this->saveOverallProgressData($overallProgressData); 253 | } 254 | 255 | $this->localKey = $name; 256 | 257 | $this->makeSureLocalIsPartOfTheCalc(); 258 | 259 | return $this; 260 | } 261 | 262 | /** 263 | * Save the overall progress data to the storage. 264 | * 265 | * @param array $progressData 266 | * @return static 267 | */ 268 | protected function saveOverallProgressData(array $progressData): static 269 | { 270 | if ($this->customSaveData !== null) { 271 | call_user_func( 272 | $this->customSaveData, 273 | $this->getStorageKeyName(), 274 | $progressData, 275 | $this->getTTL() 276 | ); 277 | } else { 278 | Cache::put($this->getStorageKeyName(), $progressData, $this->getTTL()); 279 | } 280 | 281 | return $this; 282 | } 283 | 284 | /** 285 | * Get the storage time-to-live in minutes. 286 | * 287 | * @return int 288 | */ 289 | public function getTTL(): int 290 | { 291 | return $this->customTTL ?? config("progressable.ttl", $this->defaultTTL); 292 | } 293 | 294 | /** 295 | * Reset the overall progress. 296 | * 297 | * @return static 298 | */ 299 | public function resetOverallProgress(): static 300 | { 301 | return $this->saveOverallProgressData([]); 302 | } 303 | 304 | /** 305 | * Set the prefix storage key. 306 | * 307 | * @param string $prefixStorageKey The prefix storage key to set. 308 | * @throw UniqueNameAlreadySetException If the unique name has already been set 309 | * @return static 310 | */ 311 | public function setPrefixStorageKey(string $prefixStorageKey): static 312 | { 313 | if (isset($this->overallUniqueName)) { 314 | throw new UniqueNameAlreadySetException(); 315 | } 316 | 317 | $this->customPrefixStorageKey = $prefixStorageKey; 318 | 319 | return $this; 320 | } 321 | 322 | /** 323 | * Set the time-to-live for the object. 324 | * 325 | * @param int $defaultTTL The time-to-live value to set 326 | * @return static 327 | */ 328 | public function setTTL(int $TTL): static 329 | { 330 | $this->customTTL = $TTL; 331 | return $this; 332 | } 333 | } 334 | -------------------------------------------------------------------------------- /src/ProgressableServiceProvider.php: -------------------------------------------------------------------------------- 1 | mergeConfigFrom(__DIR__.'/../config/progressable.php', 'progressable'); 17 | } 18 | 19 | /** 20 | * Bootstrap services. 21 | * 22 | * @return void 23 | */ 24 | public function boot(): void 25 | { 26 | if ($this->app->runningInConsole()) { 27 | $this->publishesConfig(); 28 | } 29 | } 30 | 31 | /** 32 | * Publish config file 33 | */ 34 | protected function publishesConfig(): void 35 | { 36 | $this->publishes([ 37 | __DIR__.'/../config/progressable.php' => config_path('progressable.php'), 38 | ], 'config'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /tests/ProgressableTest.php: -------------------------------------------------------------------------------- 1 | setOverallUniqueName('test'); 16 | $this->assertEquals('test', $this->getOverallUniqueName()); 17 | } 18 | 19 | public function testUpdateLocalProgress() 20 | { 21 | $this->setOverallUniqueName('test'); 22 | $this->setLocalProgress(50); 23 | $this->assertEquals(50, $this->getLocalProgress()); 24 | } 25 | 26 | public function testUpdateLocalProgressBounds() 27 | { 28 | $this->setOverallUniqueName('test'); 29 | $this->setLocalProgress(-10); 30 | $this->assertEquals(0, $this->getLocalProgress()); 31 | $this->setLocalProgress(120); 32 | $this->assertEquals(100, $this->getLocalProgress()); 33 | } 34 | 35 | public function testGetOverallProgress() 36 | { 37 | $uniqueName = 'test'; 38 | $this->setOverallUniqueName($uniqueName); 39 | $this->setLocalProgress(25); 40 | 41 | $obj2 = new class { 42 | use Progressable; 43 | }; 44 | $obj2->setOverallUniqueName($uniqueName); 45 | $obj2->setLocalProgress(75); 46 | 47 | $this->assertEquals(50, $this->getOverallProgress()); 48 | } 49 | 50 | public function testGetOverallProgressData() 51 | { 52 | $this->setOverallUniqueName('test'); 53 | $this->setLocalProgress(50); 54 | 55 | $progressData = $this->getOverallProgressData(); 56 | $this->assertArrayHasKey($this->getLocalKey(), $progressData); 57 | $this->assertEquals(50, $progressData[$this->getLocalKey()]['progress']); 58 | } 59 | 60 | public function testUpdateLocalProgressWithoutUniqueName() 61 | { 62 | $this->overallUniqueName = ''; 63 | $this->expectException(UniqueNameNotSetException::class); 64 | $this->setLocalProgress(50); 65 | } 66 | 67 | public function testResetLocalProgress() 68 | { 69 | $this->setOverallUniqueName('test'); 70 | $this->setLocalProgress(50); 71 | $this->resetLocalProgress(); 72 | $this->assertEquals(0, $this->getLocalProgress()); 73 | } 74 | 75 | public function testResetOverallProgress() 76 | { 77 | $this->setOverallUniqueName('test'); 78 | $this->setLocalProgress(50); 79 | 80 | $obj2 = new class { 81 | use Progressable; 82 | }; 83 | $obj2->setOverallUniqueName('test'); 84 | $obj2->setLocalProgress(75); 85 | 86 | $this->assertNotEquals(0, $this->getOverallProgress()); 87 | $this->resetOverallProgress(); 88 | $this->assertEquals(0, $this->getOverallProgress()); 89 | } 90 | 91 | public function testSetLocalKey() 92 | { 93 | $this->setOverallUniqueName('test'); 94 | $this->setLocalKey('my_custom_key'); 95 | $progressData = $this->getOverallProgressData(); 96 | $this->assertArrayHasKey('my_custom_key', $progressData); 97 | } 98 | 99 | public function testSetPrefixStorageKey() 100 | { 101 | $this->setPrefixStorageKey('custom_prefix'); 102 | $this->setOverallUniqueName('test'); 103 | $this->assertEquals('custom_prefix_test', $this->getStorageKeyName()); 104 | 105 | } 106 | 107 | public function testSetTTL() 108 | { 109 | $this->setOverallUniqueName('test'); 110 | $this->setLocalProgress(50); 111 | $this->setTTL(60); // 1 heure 112 | 113 | $ttl = $this->getTTL(); 114 | $this->assertEquals(60, $ttl); 115 | } 116 | 117 | public function testCustomSaveAndGetData() 118 | { 119 | $storage = []; 120 | 121 | $saveCallback = function ($key, $data, $ttl) use (&$storage) { 122 | $storage[$key] = $data; 123 | }; 124 | 125 | $getCallback = function ($key) use (&$storage) { 126 | return $storage[$key] ?? []; 127 | }; 128 | 129 | $this->setCustomSaveData($saveCallback); 130 | $this->setCustomGetData($getCallback); 131 | 132 | $this->setOverallUniqueName('custom_test'); 133 | $this->setLocalProgress(50); 134 | 135 | $progressData = $this->getOverallProgressData(); 136 | $this->assertArrayHasKey($this->getLocalKey(), $progressData); 137 | $this->assertEquals(50, $progressData[$this->getLocalKey()]['progress']); 138 | } 139 | } 140 | --------------------------------------------------------------------------------