├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── phpstan.neon ├── phpunit.xml ├── src ├── Event.php ├── Watcher.php └── Watching │ ├── EventInfo.php │ ├── EventTrait.php │ └── WatchedItem.php └── tests ├── TWatcher.php └── WatcherTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor/ 3 | 4 | ./composer.lock 5 | /.test1 6 | /.test2 7 | /.test.php 8 | .phpunit.result.cache 9 | 10 | /tests/bait -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 phprtc 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 | # File Watcher 2 | 3 | PHP-based file system changes watcher implemented using [**Swoole**](https://swoole.co.uk) & **Inotify**. 4 | 5 | ## Installation 6 | 7 | ``` 8 | composer require phprtc/watcher ^0.1 --dev 9 | ``` 10 | 11 | ## Usage 12 | 13 | #### Basic Usage 14 | 15 | ```php 16 | use RTC\Watcher\Watcher; 17 | use RTC\Watcher\Watching\EventInfo; 18 | 19 | require 'vendor/autoload.php'; 20 | 21 | Watcher::create() 22 | ->addPath(__DIR__ . '/app') 23 | ->addPath(__DIR__ . '/views') 24 | ->onChange(function (EventInfo $eventInfo) { 25 | echo $eventInfo->getWatchedItem()->getFullPath() . PHP_EOL; 26 | }) 27 | ->watch(); 28 | ``` 29 | 30 | #### Any Event 31 | 32 | Listens to any event on given path 33 | 34 | Be careful using this method. 35 | 36 | ```php 37 | use RTC\Watcher\Watcher; 38 | use RTC\Watcher\Watching\EventInfo; 39 | 40 | require 'vendor/autoload.php'; 41 | 42 | Watcher::create() 43 | ->addPath(__DIR__ . '/app') 44 | ->onAny(function (EventInfo $eventInfo) { 45 | echo date('H:i:s') . " - {$eventInfo->getName()} {$eventInfo->getWatchedItem()->getFullPath()}\n"; 46 | }) 47 | ->watch(); 48 | ``` 49 | 50 | #### Ignoring Path 51 | 52 | Ignore files using regular expression 53 | 54 | ```php 55 | use RTC\Watcher\Watcher; 56 | use RTC\Watcher\Watching\EventInfo; 57 | 58 | require 'vendor/autoload.php'; 59 | 60 | Watcher::create() 61 | ->addPath(__DIR__ . '/app') 62 | ->ignore(__DIR__ . '/test1/t/*') // Ignore files in "/test1/t/" 63 | ->ignore([ 64 | __DIR__ . '/test1/t/.*(\.php$)', // Ignore files that end with "php" in "/test1/t/" 65 | __DIR__ . '/test1/t/.*(\.js)', // Ignore files that end with "js" in "/test1/t/" 66 | ]) 67 | ->onChange(function (EventInfo $eventInfo) { 68 | echo date('H:i:s') . " - {$eventInfo->getName()} {$eventInfo->getWatchedItem()->getFullPath()}\n"; 69 | }) 70 | ->watch(); 71 | ``` 72 | 73 | #### Filter 74 | 75 | - Make sure that the file whose event is being fired should not end with provided characters. 76 | ```php 77 | use RTC\Watcher\Watcher; 78 | use RTC\Watcher\Watching\EventInfo; 79 | 80 | require 'vendor/autoload.php'; 81 | 82 | Watcher::create() 83 | ->addPath(__DIR__ . '/app') 84 | ->fileShouldNotEndWith(['.php']) 85 | ->onChange(function (EventInfo $eventInfo) { 86 | echo $eventInfo->getWatchedItem()->getFullPath() . PHP_EOL; 87 | }) 88 | ->watch(); 89 | ``` 90 | 91 | - Only listen to event with file name that matches given extension(s). 92 | ```php 93 | use RTC\Watcher\Watcher; 94 | use RTC\Watcher\Watching\EventInfo; 95 | 96 | require 'vendor/autoload.php'; 97 | 98 | Watcher::create() 99 | ->addPath(__DIR__ . '/app') 100 | ->addExtension('php') 101 | ->onChange(function (EventInfo $eventInfo) { 102 | echo $eventInfo->getWatchedItem()->getFullPath() . PHP_EOL; 103 | }) 104 | ->watch(); 105 | ``` 106 | 107 | #### Stopping Watcher 108 | 109 | ```php 110 | 111 | use RTC\Watcher\Watcher; 112 | use RTC\Watcher\Watching\EventInfo; 113 | use Swoole\Timer; 114 | 115 | require 'vendor/autoload.php'; 116 | 117 | $watcher = Watcher::create() 118 | ->addPath(__DIR__) 119 | ->onCreate(function (EventInfo $eventInfo) { 120 | echo date('H:i:s') . ": CREATED - {$eventInfo->getWatchedItem()->getFullPath()}\n"; 121 | }); 122 | 123 | Timer::after(1000, fn() => $watcher->stop()); // Stop watching after 1 second 124 | 125 | $watcher->start(); 126 | 127 | touch(__DIR__ . '/auto-created.txt'); 128 | unlink(__DIR__ . '/auto-created.txt'); 129 | ``` 130 | 131 | #### Swoole Server Integration 132 | 133 | ```php 134 | use Swoole\Http\Request; 135 | use Swoole\Http\Response; 136 | use Swoole\Http\Server; 137 | use RTC\Watcher\Watcher; 138 | 139 | require 'vendor/autoload.php'; 140 | 141 | $server = new Server('0.0.0.0', 9000); 142 | $server->on('request', function (Request $request, Response $response) { 143 | $response->end('Hello world'); 144 | }); 145 | 146 | $server->on('start', function (Server $server) { 147 | echo "Server started at http://0.0.0.0:9000\n"; 148 | 149 | Watcher::create() 150 | ->addPath(__DIR__ . '/app') 151 | ->addPath(__DIR__ . '/views') 152 | ->onChange(fn() => $server->reload()) 153 | ->watch(); 154 | }); 155 | 156 | $server->start(); 157 | ``` -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "phprtc/watcher", 3 | "description": "Swoole File Watcher", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Ahmard", 9 | "email": "ahmardy@outlook.com" 10 | } 11 | ], 12 | "require": { 13 | "PHP": "^8.1", 14 | "ext-inotify": "*", 15 | "evenement/evenement": "^3.0" 16 | }, 17 | "require-dev": { 18 | "phpstan/phpstan": "^1.10", 19 | "phpunit/phpunit": "^9.6", 20 | "symfony/var-dumper": "^6.2", 21 | "openswoole/ide-helper": "^4.11" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "RTC\\Watcher\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "RTC\\Tests\\Watcher\\": "tests/" 31 | } 32 | }, 33 | "scripts": { 34 | "analyse" : "vendor/bin/phpstan analyse --debug", 35 | "test" : "vendor/bin/phpunit" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "2357e6c87e059e518d13e28cc738f175", 8 | "packages": [ 9 | { 10 | "name": "evenement/evenement", 11 | "version": "v3.0.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/igorw/evenement.git", 15 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/igorw/evenement/zipball/531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 20 | "reference": "531bfb9d15f8aa57454f5f0285b18bec903b8fb7", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^6.0" 28 | }, 29 | "type": "library", 30 | "autoload": { 31 | "psr-0": { 32 | "Evenement": "src" 33 | } 34 | }, 35 | "notification-url": "https://packagist.org/downloads/", 36 | "license": [ 37 | "MIT" 38 | ], 39 | "authors": [ 40 | { 41 | "name": "Igor Wiedler", 42 | "email": "igor@wiedler.ch" 43 | } 44 | ], 45 | "description": "Événement is a very simple event dispatching library for PHP", 46 | "keywords": [ 47 | "event-dispatcher", 48 | "event-emitter" 49 | ], 50 | "support": { 51 | "issues": "https://github.com/igorw/evenement/issues", 52 | "source": "https://github.com/igorw/evenement/tree/master" 53 | }, 54 | "time": "2017-07-23T21:35:13+00:00" 55 | } 56 | ], 57 | "packages-dev": [ 58 | { 59 | "name": "doctrine/instantiator", 60 | "version": "2.0.0", 61 | "source": { 62 | "type": "git", 63 | "url": "https://github.com/doctrine/instantiator.git", 64 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" 65 | }, 66 | "dist": { 67 | "type": "zip", 68 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 69 | "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", 70 | "shasum": "" 71 | }, 72 | "require": { 73 | "php": "^8.1" 74 | }, 75 | "require-dev": { 76 | "doctrine/coding-standard": "^11", 77 | "ext-pdo": "*", 78 | "ext-phar": "*", 79 | "phpbench/phpbench": "^1.2", 80 | "phpstan/phpstan": "^1.9.4", 81 | "phpstan/phpstan-phpunit": "^1.3", 82 | "phpunit/phpunit": "^9.5.27", 83 | "vimeo/psalm": "^5.4" 84 | }, 85 | "type": "library", 86 | "autoload": { 87 | "psr-4": { 88 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 89 | } 90 | }, 91 | "notification-url": "https://packagist.org/downloads/", 92 | "license": [ 93 | "MIT" 94 | ], 95 | "authors": [ 96 | { 97 | "name": "Marco Pivetta", 98 | "email": "ocramius@gmail.com", 99 | "homepage": "https://ocramius.github.io/" 100 | } 101 | ], 102 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 103 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 104 | "keywords": [ 105 | "constructor", 106 | "instantiate" 107 | ], 108 | "support": { 109 | "issues": "https://github.com/doctrine/instantiator/issues", 110 | "source": "https://github.com/doctrine/instantiator/tree/2.0.0" 111 | }, 112 | "funding": [ 113 | { 114 | "url": "https://www.doctrine-project.org/sponsorship.html", 115 | "type": "custom" 116 | }, 117 | { 118 | "url": "https://www.patreon.com/phpdoctrine", 119 | "type": "patreon" 120 | }, 121 | { 122 | "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", 123 | "type": "tidelift" 124 | } 125 | ], 126 | "time": "2022-12-30T00:23:10+00:00" 127 | }, 128 | { 129 | "name": "myclabs/deep-copy", 130 | "version": "1.11.1", 131 | "source": { 132 | "type": "git", 133 | "url": "https://github.com/myclabs/DeepCopy.git", 134 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" 135 | }, 136 | "dist": { 137 | "type": "zip", 138 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 139 | "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", 140 | "shasum": "" 141 | }, 142 | "require": { 143 | "php": "^7.1 || ^8.0" 144 | }, 145 | "conflict": { 146 | "doctrine/collections": "<1.6.8", 147 | "doctrine/common": "<2.13.3 || >=3,<3.2.2" 148 | }, 149 | "require-dev": { 150 | "doctrine/collections": "^1.6.8", 151 | "doctrine/common": "^2.13.3 || ^3.2.2", 152 | "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" 153 | }, 154 | "type": "library", 155 | "autoload": { 156 | "files": [ 157 | "src/DeepCopy/deep_copy.php" 158 | ], 159 | "psr-4": { 160 | "DeepCopy\\": "src/DeepCopy/" 161 | } 162 | }, 163 | "notification-url": "https://packagist.org/downloads/", 164 | "license": [ 165 | "MIT" 166 | ], 167 | "description": "Create deep copies (clones) of your objects", 168 | "keywords": [ 169 | "clone", 170 | "copy", 171 | "duplicate", 172 | "object", 173 | "object graph" 174 | ], 175 | "support": { 176 | "issues": "https://github.com/myclabs/DeepCopy/issues", 177 | "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" 178 | }, 179 | "funding": [ 180 | { 181 | "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", 182 | "type": "tidelift" 183 | } 184 | ], 185 | "time": "2023-03-08T13:26:56+00:00" 186 | }, 187 | { 188 | "name": "nikic/php-parser", 189 | "version": "v4.15.4", 190 | "source": { 191 | "type": "git", 192 | "url": "https://github.com/nikic/PHP-Parser.git", 193 | "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" 194 | }, 195 | "dist": { 196 | "type": "zip", 197 | "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", 198 | "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", 199 | "shasum": "" 200 | }, 201 | "require": { 202 | "ext-tokenizer": "*", 203 | "php": ">=7.0" 204 | }, 205 | "require-dev": { 206 | "ircmaxell/php-yacc": "^0.0.7", 207 | "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" 208 | }, 209 | "bin": [ 210 | "bin/php-parse" 211 | ], 212 | "type": "library", 213 | "extra": { 214 | "branch-alias": { 215 | "dev-master": "4.9-dev" 216 | } 217 | }, 218 | "autoload": { 219 | "psr-4": { 220 | "PhpParser\\": "lib/PhpParser" 221 | } 222 | }, 223 | "notification-url": "https://packagist.org/downloads/", 224 | "license": [ 225 | "BSD-3-Clause" 226 | ], 227 | "authors": [ 228 | { 229 | "name": "Nikita Popov" 230 | } 231 | ], 232 | "description": "A PHP parser written in PHP", 233 | "keywords": [ 234 | "parser", 235 | "php" 236 | ], 237 | "support": { 238 | "issues": "https://github.com/nikic/PHP-Parser/issues", 239 | "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" 240 | }, 241 | "time": "2023-03-05T19:49:14+00:00" 242 | }, 243 | { 244 | "name": "openswoole/ide-helper", 245 | "version": "4.11.6", 246 | "source": { 247 | "type": "git", 248 | "url": "https://github.com/openswoole/ide-helper.git", 249 | "reference": "6e8831af6dd34e24456cb98272373b030bce94df" 250 | }, 251 | "dist": { 252 | "type": "zip", 253 | "url": "https://api.github.com/repos/openswoole/ide-helper/zipball/6e8831af6dd34e24456cb98272373b030bce94df", 254 | "reference": "6e8831af6dd34e24456cb98272373b030bce94df", 255 | "shasum": "" 256 | }, 257 | "require": { 258 | "php": ">=7.4" 259 | }, 260 | "require-dev": { 261 | "friendsofphp/php-cs-fixer": "^3.3" 262 | }, 263 | "type": "library", 264 | "notification-url": "https://packagist.org/downloads/", 265 | "license": [ 266 | "Apache-2.0" 267 | ], 268 | "authors": [ 269 | { 270 | "name": "Open Swoole Group", 271 | "email": "hello@openswoole.com" 272 | } 273 | ], 274 | "description": "IDE help files for Open Swoole.", 275 | "support": { 276 | "issues": "https://github.com/openswoole/ide-helper/issues", 277 | "source": "https://github.com/openswoole/ide-helper/tree/4.11.6" 278 | }, 279 | "time": "2022-08-03T13:28:52+00:00" 280 | }, 281 | { 282 | "name": "phar-io/manifest", 283 | "version": "2.0.3", 284 | "source": { 285 | "type": "git", 286 | "url": "https://github.com/phar-io/manifest.git", 287 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53" 288 | }, 289 | "dist": { 290 | "type": "zip", 291 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", 292 | "reference": "97803eca37d319dfa7826cc2437fc020857acb53", 293 | "shasum": "" 294 | }, 295 | "require": { 296 | "ext-dom": "*", 297 | "ext-phar": "*", 298 | "ext-xmlwriter": "*", 299 | "phar-io/version": "^3.0.1", 300 | "php": "^7.2 || ^8.0" 301 | }, 302 | "type": "library", 303 | "extra": { 304 | "branch-alias": { 305 | "dev-master": "2.0.x-dev" 306 | } 307 | }, 308 | "autoload": { 309 | "classmap": [ 310 | "src/" 311 | ] 312 | }, 313 | "notification-url": "https://packagist.org/downloads/", 314 | "license": [ 315 | "BSD-3-Clause" 316 | ], 317 | "authors": [ 318 | { 319 | "name": "Arne Blankerts", 320 | "email": "arne@blankerts.de", 321 | "role": "Developer" 322 | }, 323 | { 324 | "name": "Sebastian Heuer", 325 | "email": "sebastian@phpeople.de", 326 | "role": "Developer" 327 | }, 328 | { 329 | "name": "Sebastian Bergmann", 330 | "email": "sebastian@phpunit.de", 331 | "role": "Developer" 332 | } 333 | ], 334 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 335 | "support": { 336 | "issues": "https://github.com/phar-io/manifest/issues", 337 | "source": "https://github.com/phar-io/manifest/tree/2.0.3" 338 | }, 339 | "time": "2021-07-20T11:28:43+00:00" 340 | }, 341 | { 342 | "name": "phar-io/version", 343 | "version": "3.2.1", 344 | "source": { 345 | "type": "git", 346 | "url": "https://github.com/phar-io/version.git", 347 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74" 348 | }, 349 | "dist": { 350 | "type": "zip", 351 | "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 352 | "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74", 353 | "shasum": "" 354 | }, 355 | "require": { 356 | "php": "^7.2 || ^8.0" 357 | }, 358 | "type": "library", 359 | "autoload": { 360 | "classmap": [ 361 | "src/" 362 | ] 363 | }, 364 | "notification-url": "https://packagist.org/downloads/", 365 | "license": [ 366 | "BSD-3-Clause" 367 | ], 368 | "authors": [ 369 | { 370 | "name": "Arne Blankerts", 371 | "email": "arne@blankerts.de", 372 | "role": "Developer" 373 | }, 374 | { 375 | "name": "Sebastian Heuer", 376 | "email": "sebastian@phpeople.de", 377 | "role": "Developer" 378 | }, 379 | { 380 | "name": "Sebastian Bergmann", 381 | "email": "sebastian@phpunit.de", 382 | "role": "Developer" 383 | } 384 | ], 385 | "description": "Library for handling version information and constraints", 386 | "support": { 387 | "issues": "https://github.com/phar-io/version/issues", 388 | "source": "https://github.com/phar-io/version/tree/3.2.1" 389 | }, 390 | "time": "2022-02-21T01:04:05+00:00" 391 | }, 392 | { 393 | "name": "phpstan/phpstan", 394 | "version": "1.10.7", 395 | "source": { 396 | "type": "git", 397 | "url": "https://github.com/phpstan/phpstan.git", 398 | "reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975" 399 | }, 400 | "dist": { 401 | "type": "zip", 402 | "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b10ceb526d9607903c5b2673f1fc8775dbe48975", 403 | "reference": "b10ceb526d9607903c5b2673f1fc8775dbe48975", 404 | "shasum": "" 405 | }, 406 | "require": { 407 | "php": "^7.2|^8.0" 408 | }, 409 | "conflict": { 410 | "phpstan/phpstan-shim": "*" 411 | }, 412 | "bin": [ 413 | "phpstan", 414 | "phpstan.phar" 415 | ], 416 | "type": "library", 417 | "autoload": { 418 | "files": [ 419 | "bootstrap.php" 420 | ] 421 | }, 422 | "notification-url": "https://packagist.org/downloads/", 423 | "license": [ 424 | "MIT" 425 | ], 426 | "description": "PHPStan - PHP Static Analysis Tool", 427 | "keywords": [ 428 | "dev", 429 | "static analysis" 430 | ], 431 | "support": { 432 | "docs": "https://phpstan.org/user-guide/getting-started", 433 | "forum": "https://github.com/phpstan/phpstan/discussions", 434 | "issues": "https://github.com/phpstan/phpstan/issues", 435 | "security": "https://github.com/phpstan/phpstan/security/policy", 436 | "source": "https://github.com/phpstan/phpstan-src" 437 | }, 438 | "funding": [ 439 | { 440 | "url": "https://github.com/ondrejmirtes", 441 | "type": "github" 442 | }, 443 | { 444 | "url": "https://github.com/phpstan", 445 | "type": "github" 446 | }, 447 | { 448 | "url": "https://tidelift.com/funding/github/packagist/phpstan/phpstan", 449 | "type": "tidelift" 450 | } 451 | ], 452 | "time": "2023-03-16T15:24:20+00:00" 453 | }, 454 | { 455 | "name": "phpunit/php-code-coverage", 456 | "version": "9.2.26", 457 | "source": { 458 | "type": "git", 459 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 460 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1" 461 | }, 462 | "dist": { 463 | "type": "zip", 464 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 465 | "reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1", 466 | "shasum": "" 467 | }, 468 | "require": { 469 | "ext-dom": "*", 470 | "ext-libxml": "*", 471 | "ext-xmlwriter": "*", 472 | "nikic/php-parser": "^4.15", 473 | "php": ">=7.3", 474 | "phpunit/php-file-iterator": "^3.0.3", 475 | "phpunit/php-text-template": "^2.0.2", 476 | "sebastian/code-unit-reverse-lookup": "^2.0.2", 477 | "sebastian/complexity": "^2.0", 478 | "sebastian/environment": "^5.1.2", 479 | "sebastian/lines-of-code": "^1.0.3", 480 | "sebastian/version": "^3.0.1", 481 | "theseer/tokenizer": "^1.2.0" 482 | }, 483 | "require-dev": { 484 | "phpunit/phpunit": "^9.3" 485 | }, 486 | "suggest": { 487 | "ext-pcov": "PHP extension that provides line coverage", 488 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 489 | }, 490 | "type": "library", 491 | "extra": { 492 | "branch-alias": { 493 | "dev-master": "9.2-dev" 494 | } 495 | }, 496 | "autoload": { 497 | "classmap": [ 498 | "src/" 499 | ] 500 | }, 501 | "notification-url": "https://packagist.org/downloads/", 502 | "license": [ 503 | "BSD-3-Clause" 504 | ], 505 | "authors": [ 506 | { 507 | "name": "Sebastian Bergmann", 508 | "email": "sebastian@phpunit.de", 509 | "role": "lead" 510 | } 511 | ], 512 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 513 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 514 | "keywords": [ 515 | "coverage", 516 | "testing", 517 | "xunit" 518 | ], 519 | "support": { 520 | "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", 521 | "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26" 522 | }, 523 | "funding": [ 524 | { 525 | "url": "https://github.com/sebastianbergmann", 526 | "type": "github" 527 | } 528 | ], 529 | "time": "2023-03-06T12:58:08+00:00" 530 | }, 531 | { 532 | "name": "phpunit/php-file-iterator", 533 | "version": "3.0.6", 534 | "source": { 535 | "type": "git", 536 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 537 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" 538 | }, 539 | "dist": { 540 | "type": "zip", 541 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 542 | "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", 543 | "shasum": "" 544 | }, 545 | "require": { 546 | "php": ">=7.3" 547 | }, 548 | "require-dev": { 549 | "phpunit/phpunit": "^9.3" 550 | }, 551 | "type": "library", 552 | "extra": { 553 | "branch-alias": { 554 | "dev-master": "3.0-dev" 555 | } 556 | }, 557 | "autoload": { 558 | "classmap": [ 559 | "src/" 560 | ] 561 | }, 562 | "notification-url": "https://packagist.org/downloads/", 563 | "license": [ 564 | "BSD-3-Clause" 565 | ], 566 | "authors": [ 567 | { 568 | "name": "Sebastian Bergmann", 569 | "email": "sebastian@phpunit.de", 570 | "role": "lead" 571 | } 572 | ], 573 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 574 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 575 | "keywords": [ 576 | "filesystem", 577 | "iterator" 578 | ], 579 | "support": { 580 | "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", 581 | "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" 582 | }, 583 | "funding": [ 584 | { 585 | "url": "https://github.com/sebastianbergmann", 586 | "type": "github" 587 | } 588 | ], 589 | "time": "2021-12-02T12:48:52+00:00" 590 | }, 591 | { 592 | "name": "phpunit/php-invoker", 593 | "version": "3.1.1", 594 | "source": { 595 | "type": "git", 596 | "url": "https://github.com/sebastianbergmann/php-invoker.git", 597 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" 598 | }, 599 | "dist": { 600 | "type": "zip", 601 | "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 602 | "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", 603 | "shasum": "" 604 | }, 605 | "require": { 606 | "php": ">=7.3" 607 | }, 608 | "require-dev": { 609 | "ext-pcntl": "*", 610 | "phpunit/phpunit": "^9.3" 611 | }, 612 | "suggest": { 613 | "ext-pcntl": "*" 614 | }, 615 | "type": "library", 616 | "extra": { 617 | "branch-alias": { 618 | "dev-master": "3.1-dev" 619 | } 620 | }, 621 | "autoload": { 622 | "classmap": [ 623 | "src/" 624 | ] 625 | }, 626 | "notification-url": "https://packagist.org/downloads/", 627 | "license": [ 628 | "BSD-3-Clause" 629 | ], 630 | "authors": [ 631 | { 632 | "name": "Sebastian Bergmann", 633 | "email": "sebastian@phpunit.de", 634 | "role": "lead" 635 | } 636 | ], 637 | "description": "Invoke callables with a timeout", 638 | "homepage": "https://github.com/sebastianbergmann/php-invoker/", 639 | "keywords": [ 640 | "process" 641 | ], 642 | "support": { 643 | "issues": "https://github.com/sebastianbergmann/php-invoker/issues", 644 | "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" 645 | }, 646 | "funding": [ 647 | { 648 | "url": "https://github.com/sebastianbergmann", 649 | "type": "github" 650 | } 651 | ], 652 | "time": "2020-09-28T05:58:55+00:00" 653 | }, 654 | { 655 | "name": "phpunit/php-text-template", 656 | "version": "2.0.4", 657 | "source": { 658 | "type": "git", 659 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 660 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" 661 | }, 662 | "dist": { 663 | "type": "zip", 664 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 665 | "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", 666 | "shasum": "" 667 | }, 668 | "require": { 669 | "php": ">=7.3" 670 | }, 671 | "require-dev": { 672 | "phpunit/phpunit": "^9.3" 673 | }, 674 | "type": "library", 675 | "extra": { 676 | "branch-alias": { 677 | "dev-master": "2.0-dev" 678 | } 679 | }, 680 | "autoload": { 681 | "classmap": [ 682 | "src/" 683 | ] 684 | }, 685 | "notification-url": "https://packagist.org/downloads/", 686 | "license": [ 687 | "BSD-3-Clause" 688 | ], 689 | "authors": [ 690 | { 691 | "name": "Sebastian Bergmann", 692 | "email": "sebastian@phpunit.de", 693 | "role": "lead" 694 | } 695 | ], 696 | "description": "Simple template engine.", 697 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 698 | "keywords": [ 699 | "template" 700 | ], 701 | "support": { 702 | "issues": "https://github.com/sebastianbergmann/php-text-template/issues", 703 | "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" 704 | }, 705 | "funding": [ 706 | { 707 | "url": "https://github.com/sebastianbergmann", 708 | "type": "github" 709 | } 710 | ], 711 | "time": "2020-10-26T05:33:50+00:00" 712 | }, 713 | { 714 | "name": "phpunit/php-timer", 715 | "version": "5.0.3", 716 | "source": { 717 | "type": "git", 718 | "url": "https://github.com/sebastianbergmann/php-timer.git", 719 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" 720 | }, 721 | "dist": { 722 | "type": "zip", 723 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 724 | "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", 725 | "shasum": "" 726 | }, 727 | "require": { 728 | "php": ">=7.3" 729 | }, 730 | "require-dev": { 731 | "phpunit/phpunit": "^9.3" 732 | }, 733 | "type": "library", 734 | "extra": { 735 | "branch-alias": { 736 | "dev-master": "5.0-dev" 737 | } 738 | }, 739 | "autoload": { 740 | "classmap": [ 741 | "src/" 742 | ] 743 | }, 744 | "notification-url": "https://packagist.org/downloads/", 745 | "license": [ 746 | "BSD-3-Clause" 747 | ], 748 | "authors": [ 749 | { 750 | "name": "Sebastian Bergmann", 751 | "email": "sebastian@phpunit.de", 752 | "role": "lead" 753 | } 754 | ], 755 | "description": "Utility class for timing", 756 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 757 | "keywords": [ 758 | "timer" 759 | ], 760 | "support": { 761 | "issues": "https://github.com/sebastianbergmann/php-timer/issues", 762 | "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" 763 | }, 764 | "funding": [ 765 | { 766 | "url": "https://github.com/sebastianbergmann", 767 | "type": "github" 768 | } 769 | ], 770 | "time": "2020-10-26T13:16:10+00:00" 771 | }, 772 | { 773 | "name": "phpunit/phpunit", 774 | "version": "9.6.5", 775 | "source": { 776 | "type": "git", 777 | "url": "https://github.com/sebastianbergmann/phpunit.git", 778 | "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5" 779 | }, 780 | "dist": { 781 | "type": "zip", 782 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/86e761949019ae83f49240b2f2123fb5ab3b2fc5", 783 | "reference": "86e761949019ae83f49240b2f2123fb5ab3b2fc5", 784 | "shasum": "" 785 | }, 786 | "require": { 787 | "doctrine/instantiator": "^1.3.1 || ^2", 788 | "ext-dom": "*", 789 | "ext-json": "*", 790 | "ext-libxml": "*", 791 | "ext-mbstring": "*", 792 | "ext-xml": "*", 793 | "ext-xmlwriter": "*", 794 | "myclabs/deep-copy": "^1.10.1", 795 | "phar-io/manifest": "^2.0.3", 796 | "phar-io/version": "^3.0.2", 797 | "php": ">=7.3", 798 | "phpunit/php-code-coverage": "^9.2.13", 799 | "phpunit/php-file-iterator": "^3.0.5", 800 | "phpunit/php-invoker": "^3.1.1", 801 | "phpunit/php-text-template": "^2.0.3", 802 | "phpunit/php-timer": "^5.0.2", 803 | "sebastian/cli-parser": "^1.0.1", 804 | "sebastian/code-unit": "^1.0.6", 805 | "sebastian/comparator": "^4.0.8", 806 | "sebastian/diff": "^4.0.3", 807 | "sebastian/environment": "^5.1.3", 808 | "sebastian/exporter": "^4.0.5", 809 | "sebastian/global-state": "^5.0.1", 810 | "sebastian/object-enumerator": "^4.0.3", 811 | "sebastian/resource-operations": "^3.0.3", 812 | "sebastian/type": "^3.2", 813 | "sebastian/version": "^3.0.2" 814 | }, 815 | "suggest": { 816 | "ext-soap": "To be able to generate mocks based on WSDL files", 817 | "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" 818 | }, 819 | "bin": [ 820 | "phpunit" 821 | ], 822 | "type": "library", 823 | "extra": { 824 | "branch-alias": { 825 | "dev-master": "9.6-dev" 826 | } 827 | }, 828 | "autoload": { 829 | "files": [ 830 | "src/Framework/Assert/Functions.php" 831 | ], 832 | "classmap": [ 833 | "src/" 834 | ] 835 | }, 836 | "notification-url": "https://packagist.org/downloads/", 837 | "license": [ 838 | "BSD-3-Clause" 839 | ], 840 | "authors": [ 841 | { 842 | "name": "Sebastian Bergmann", 843 | "email": "sebastian@phpunit.de", 844 | "role": "lead" 845 | } 846 | ], 847 | "description": "The PHP Unit Testing framework.", 848 | "homepage": "https://phpunit.de/", 849 | "keywords": [ 850 | "phpunit", 851 | "testing", 852 | "xunit" 853 | ], 854 | "support": { 855 | "issues": "https://github.com/sebastianbergmann/phpunit/issues", 856 | "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.5" 857 | }, 858 | "funding": [ 859 | { 860 | "url": "https://phpunit.de/sponsors.html", 861 | "type": "custom" 862 | }, 863 | { 864 | "url": "https://github.com/sebastianbergmann", 865 | "type": "github" 866 | }, 867 | { 868 | "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", 869 | "type": "tidelift" 870 | } 871 | ], 872 | "time": "2023-03-09T06:34:10+00:00" 873 | }, 874 | { 875 | "name": "sebastian/cli-parser", 876 | "version": "1.0.1", 877 | "source": { 878 | "type": "git", 879 | "url": "https://github.com/sebastianbergmann/cli-parser.git", 880 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" 881 | }, 882 | "dist": { 883 | "type": "zip", 884 | "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", 885 | "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", 886 | "shasum": "" 887 | }, 888 | "require": { 889 | "php": ">=7.3" 890 | }, 891 | "require-dev": { 892 | "phpunit/phpunit": "^9.3" 893 | }, 894 | "type": "library", 895 | "extra": { 896 | "branch-alias": { 897 | "dev-master": "1.0-dev" 898 | } 899 | }, 900 | "autoload": { 901 | "classmap": [ 902 | "src/" 903 | ] 904 | }, 905 | "notification-url": "https://packagist.org/downloads/", 906 | "license": [ 907 | "BSD-3-Clause" 908 | ], 909 | "authors": [ 910 | { 911 | "name": "Sebastian Bergmann", 912 | "email": "sebastian@phpunit.de", 913 | "role": "lead" 914 | } 915 | ], 916 | "description": "Library for parsing CLI options", 917 | "homepage": "https://github.com/sebastianbergmann/cli-parser", 918 | "support": { 919 | "issues": "https://github.com/sebastianbergmann/cli-parser/issues", 920 | "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" 921 | }, 922 | "funding": [ 923 | { 924 | "url": "https://github.com/sebastianbergmann", 925 | "type": "github" 926 | } 927 | ], 928 | "time": "2020-09-28T06:08:49+00:00" 929 | }, 930 | { 931 | "name": "sebastian/code-unit", 932 | "version": "1.0.8", 933 | "source": { 934 | "type": "git", 935 | "url": "https://github.com/sebastianbergmann/code-unit.git", 936 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" 937 | }, 938 | "dist": { 939 | "type": "zip", 940 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", 941 | "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", 942 | "shasum": "" 943 | }, 944 | "require": { 945 | "php": ">=7.3" 946 | }, 947 | "require-dev": { 948 | "phpunit/phpunit": "^9.3" 949 | }, 950 | "type": "library", 951 | "extra": { 952 | "branch-alias": { 953 | "dev-master": "1.0-dev" 954 | } 955 | }, 956 | "autoload": { 957 | "classmap": [ 958 | "src/" 959 | ] 960 | }, 961 | "notification-url": "https://packagist.org/downloads/", 962 | "license": [ 963 | "BSD-3-Clause" 964 | ], 965 | "authors": [ 966 | { 967 | "name": "Sebastian Bergmann", 968 | "email": "sebastian@phpunit.de", 969 | "role": "lead" 970 | } 971 | ], 972 | "description": "Collection of value objects that represent the PHP code units", 973 | "homepage": "https://github.com/sebastianbergmann/code-unit", 974 | "support": { 975 | "issues": "https://github.com/sebastianbergmann/code-unit/issues", 976 | "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" 977 | }, 978 | "funding": [ 979 | { 980 | "url": "https://github.com/sebastianbergmann", 981 | "type": "github" 982 | } 983 | ], 984 | "time": "2020-10-26T13:08:54+00:00" 985 | }, 986 | { 987 | "name": "sebastian/code-unit-reverse-lookup", 988 | "version": "2.0.3", 989 | "source": { 990 | "type": "git", 991 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 992 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" 993 | }, 994 | "dist": { 995 | "type": "zip", 996 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 997 | "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", 998 | "shasum": "" 999 | }, 1000 | "require": { 1001 | "php": ">=7.3" 1002 | }, 1003 | "require-dev": { 1004 | "phpunit/phpunit": "^9.3" 1005 | }, 1006 | "type": "library", 1007 | "extra": { 1008 | "branch-alias": { 1009 | "dev-master": "2.0-dev" 1010 | } 1011 | }, 1012 | "autoload": { 1013 | "classmap": [ 1014 | "src/" 1015 | ] 1016 | }, 1017 | "notification-url": "https://packagist.org/downloads/", 1018 | "license": [ 1019 | "BSD-3-Clause" 1020 | ], 1021 | "authors": [ 1022 | { 1023 | "name": "Sebastian Bergmann", 1024 | "email": "sebastian@phpunit.de" 1025 | } 1026 | ], 1027 | "description": "Looks up which function or method a line of code belongs to", 1028 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 1029 | "support": { 1030 | "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", 1031 | "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" 1032 | }, 1033 | "funding": [ 1034 | { 1035 | "url": "https://github.com/sebastianbergmann", 1036 | "type": "github" 1037 | } 1038 | ], 1039 | "time": "2020-09-28T05:30:19+00:00" 1040 | }, 1041 | { 1042 | "name": "sebastian/comparator", 1043 | "version": "4.0.8", 1044 | "source": { 1045 | "type": "git", 1046 | "url": "https://github.com/sebastianbergmann/comparator.git", 1047 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a" 1048 | }, 1049 | "dist": { 1050 | "type": "zip", 1051 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/fa0f136dd2334583309d32b62544682ee972b51a", 1052 | "reference": "fa0f136dd2334583309d32b62544682ee972b51a", 1053 | "shasum": "" 1054 | }, 1055 | "require": { 1056 | "php": ">=7.3", 1057 | "sebastian/diff": "^4.0", 1058 | "sebastian/exporter": "^4.0" 1059 | }, 1060 | "require-dev": { 1061 | "phpunit/phpunit": "^9.3" 1062 | }, 1063 | "type": "library", 1064 | "extra": { 1065 | "branch-alias": { 1066 | "dev-master": "4.0-dev" 1067 | } 1068 | }, 1069 | "autoload": { 1070 | "classmap": [ 1071 | "src/" 1072 | ] 1073 | }, 1074 | "notification-url": "https://packagist.org/downloads/", 1075 | "license": [ 1076 | "BSD-3-Clause" 1077 | ], 1078 | "authors": [ 1079 | { 1080 | "name": "Sebastian Bergmann", 1081 | "email": "sebastian@phpunit.de" 1082 | }, 1083 | { 1084 | "name": "Jeff Welch", 1085 | "email": "whatthejeff@gmail.com" 1086 | }, 1087 | { 1088 | "name": "Volker Dusch", 1089 | "email": "github@wallbash.com" 1090 | }, 1091 | { 1092 | "name": "Bernhard Schussek", 1093 | "email": "bschussek@2bepublished.at" 1094 | } 1095 | ], 1096 | "description": "Provides the functionality to compare PHP values for equality", 1097 | "homepage": "https://github.com/sebastianbergmann/comparator", 1098 | "keywords": [ 1099 | "comparator", 1100 | "compare", 1101 | "equality" 1102 | ], 1103 | "support": { 1104 | "issues": "https://github.com/sebastianbergmann/comparator/issues", 1105 | "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.8" 1106 | }, 1107 | "funding": [ 1108 | { 1109 | "url": "https://github.com/sebastianbergmann", 1110 | "type": "github" 1111 | } 1112 | ], 1113 | "time": "2022-09-14T12:41:17+00:00" 1114 | }, 1115 | { 1116 | "name": "sebastian/complexity", 1117 | "version": "2.0.2", 1118 | "source": { 1119 | "type": "git", 1120 | "url": "https://github.com/sebastianbergmann/complexity.git", 1121 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" 1122 | }, 1123 | "dist": { 1124 | "type": "zip", 1125 | "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", 1126 | "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", 1127 | "shasum": "" 1128 | }, 1129 | "require": { 1130 | "nikic/php-parser": "^4.7", 1131 | "php": ">=7.3" 1132 | }, 1133 | "require-dev": { 1134 | "phpunit/phpunit": "^9.3" 1135 | }, 1136 | "type": "library", 1137 | "extra": { 1138 | "branch-alias": { 1139 | "dev-master": "2.0-dev" 1140 | } 1141 | }, 1142 | "autoload": { 1143 | "classmap": [ 1144 | "src/" 1145 | ] 1146 | }, 1147 | "notification-url": "https://packagist.org/downloads/", 1148 | "license": [ 1149 | "BSD-3-Clause" 1150 | ], 1151 | "authors": [ 1152 | { 1153 | "name": "Sebastian Bergmann", 1154 | "email": "sebastian@phpunit.de", 1155 | "role": "lead" 1156 | } 1157 | ], 1158 | "description": "Library for calculating the complexity of PHP code units", 1159 | "homepage": "https://github.com/sebastianbergmann/complexity", 1160 | "support": { 1161 | "issues": "https://github.com/sebastianbergmann/complexity/issues", 1162 | "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" 1163 | }, 1164 | "funding": [ 1165 | { 1166 | "url": "https://github.com/sebastianbergmann", 1167 | "type": "github" 1168 | } 1169 | ], 1170 | "time": "2020-10-26T15:52:27+00:00" 1171 | }, 1172 | { 1173 | "name": "sebastian/diff", 1174 | "version": "4.0.4", 1175 | "source": { 1176 | "type": "git", 1177 | "url": "https://github.com/sebastianbergmann/diff.git", 1178 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" 1179 | }, 1180 | "dist": { 1181 | "type": "zip", 1182 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1183 | "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", 1184 | "shasum": "" 1185 | }, 1186 | "require": { 1187 | "php": ">=7.3" 1188 | }, 1189 | "require-dev": { 1190 | "phpunit/phpunit": "^9.3", 1191 | "symfony/process": "^4.2 || ^5" 1192 | }, 1193 | "type": "library", 1194 | "extra": { 1195 | "branch-alias": { 1196 | "dev-master": "4.0-dev" 1197 | } 1198 | }, 1199 | "autoload": { 1200 | "classmap": [ 1201 | "src/" 1202 | ] 1203 | }, 1204 | "notification-url": "https://packagist.org/downloads/", 1205 | "license": [ 1206 | "BSD-3-Clause" 1207 | ], 1208 | "authors": [ 1209 | { 1210 | "name": "Sebastian Bergmann", 1211 | "email": "sebastian@phpunit.de" 1212 | }, 1213 | { 1214 | "name": "Kore Nordmann", 1215 | "email": "mail@kore-nordmann.de" 1216 | } 1217 | ], 1218 | "description": "Diff implementation", 1219 | "homepage": "https://github.com/sebastianbergmann/diff", 1220 | "keywords": [ 1221 | "diff", 1222 | "udiff", 1223 | "unidiff", 1224 | "unified diff" 1225 | ], 1226 | "support": { 1227 | "issues": "https://github.com/sebastianbergmann/diff/issues", 1228 | "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" 1229 | }, 1230 | "funding": [ 1231 | { 1232 | "url": "https://github.com/sebastianbergmann", 1233 | "type": "github" 1234 | } 1235 | ], 1236 | "time": "2020-10-26T13:10:38+00:00" 1237 | }, 1238 | { 1239 | "name": "sebastian/environment", 1240 | "version": "5.1.5", 1241 | "source": { 1242 | "type": "git", 1243 | "url": "https://github.com/sebastianbergmann/environment.git", 1244 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed" 1245 | }, 1246 | "dist": { 1247 | "type": "zip", 1248 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1249 | "reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed", 1250 | "shasum": "" 1251 | }, 1252 | "require": { 1253 | "php": ">=7.3" 1254 | }, 1255 | "require-dev": { 1256 | "phpunit/phpunit": "^9.3" 1257 | }, 1258 | "suggest": { 1259 | "ext-posix": "*" 1260 | }, 1261 | "type": "library", 1262 | "extra": { 1263 | "branch-alias": { 1264 | "dev-master": "5.1-dev" 1265 | } 1266 | }, 1267 | "autoload": { 1268 | "classmap": [ 1269 | "src/" 1270 | ] 1271 | }, 1272 | "notification-url": "https://packagist.org/downloads/", 1273 | "license": [ 1274 | "BSD-3-Clause" 1275 | ], 1276 | "authors": [ 1277 | { 1278 | "name": "Sebastian Bergmann", 1279 | "email": "sebastian@phpunit.de" 1280 | } 1281 | ], 1282 | "description": "Provides functionality to handle HHVM/PHP environments", 1283 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1284 | "keywords": [ 1285 | "Xdebug", 1286 | "environment", 1287 | "hhvm" 1288 | ], 1289 | "support": { 1290 | "issues": "https://github.com/sebastianbergmann/environment/issues", 1291 | "source": "https://github.com/sebastianbergmann/environment/tree/5.1.5" 1292 | }, 1293 | "funding": [ 1294 | { 1295 | "url": "https://github.com/sebastianbergmann", 1296 | "type": "github" 1297 | } 1298 | ], 1299 | "time": "2023-02-03T06:03:51+00:00" 1300 | }, 1301 | { 1302 | "name": "sebastian/exporter", 1303 | "version": "4.0.5", 1304 | "source": { 1305 | "type": "git", 1306 | "url": "https://github.com/sebastianbergmann/exporter.git", 1307 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d" 1308 | }, 1309 | "dist": { 1310 | "type": "zip", 1311 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1312 | "reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d", 1313 | "shasum": "" 1314 | }, 1315 | "require": { 1316 | "php": ">=7.3", 1317 | "sebastian/recursion-context": "^4.0" 1318 | }, 1319 | "require-dev": { 1320 | "ext-mbstring": "*", 1321 | "phpunit/phpunit": "^9.3" 1322 | }, 1323 | "type": "library", 1324 | "extra": { 1325 | "branch-alias": { 1326 | "dev-master": "4.0-dev" 1327 | } 1328 | }, 1329 | "autoload": { 1330 | "classmap": [ 1331 | "src/" 1332 | ] 1333 | }, 1334 | "notification-url": "https://packagist.org/downloads/", 1335 | "license": [ 1336 | "BSD-3-Clause" 1337 | ], 1338 | "authors": [ 1339 | { 1340 | "name": "Sebastian Bergmann", 1341 | "email": "sebastian@phpunit.de" 1342 | }, 1343 | { 1344 | "name": "Jeff Welch", 1345 | "email": "whatthejeff@gmail.com" 1346 | }, 1347 | { 1348 | "name": "Volker Dusch", 1349 | "email": "github@wallbash.com" 1350 | }, 1351 | { 1352 | "name": "Adam Harvey", 1353 | "email": "aharvey@php.net" 1354 | }, 1355 | { 1356 | "name": "Bernhard Schussek", 1357 | "email": "bschussek@gmail.com" 1358 | } 1359 | ], 1360 | "description": "Provides the functionality to export PHP variables for visualization", 1361 | "homepage": "https://www.github.com/sebastianbergmann/exporter", 1362 | "keywords": [ 1363 | "export", 1364 | "exporter" 1365 | ], 1366 | "support": { 1367 | "issues": "https://github.com/sebastianbergmann/exporter/issues", 1368 | "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5" 1369 | }, 1370 | "funding": [ 1371 | { 1372 | "url": "https://github.com/sebastianbergmann", 1373 | "type": "github" 1374 | } 1375 | ], 1376 | "time": "2022-09-14T06:03:37+00:00" 1377 | }, 1378 | { 1379 | "name": "sebastian/global-state", 1380 | "version": "5.0.5", 1381 | "source": { 1382 | "type": "git", 1383 | "url": "https://github.com/sebastianbergmann/global-state.git", 1384 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" 1385 | }, 1386 | "dist": { 1387 | "type": "zip", 1388 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1389 | "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", 1390 | "shasum": "" 1391 | }, 1392 | "require": { 1393 | "php": ">=7.3", 1394 | "sebastian/object-reflector": "^2.0", 1395 | "sebastian/recursion-context": "^4.0" 1396 | }, 1397 | "require-dev": { 1398 | "ext-dom": "*", 1399 | "phpunit/phpunit": "^9.3" 1400 | }, 1401 | "suggest": { 1402 | "ext-uopz": "*" 1403 | }, 1404 | "type": "library", 1405 | "extra": { 1406 | "branch-alias": { 1407 | "dev-master": "5.0-dev" 1408 | } 1409 | }, 1410 | "autoload": { 1411 | "classmap": [ 1412 | "src/" 1413 | ] 1414 | }, 1415 | "notification-url": "https://packagist.org/downloads/", 1416 | "license": [ 1417 | "BSD-3-Clause" 1418 | ], 1419 | "authors": [ 1420 | { 1421 | "name": "Sebastian Bergmann", 1422 | "email": "sebastian@phpunit.de" 1423 | } 1424 | ], 1425 | "description": "Snapshotting of global state", 1426 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1427 | "keywords": [ 1428 | "global state" 1429 | ], 1430 | "support": { 1431 | "issues": "https://github.com/sebastianbergmann/global-state/issues", 1432 | "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" 1433 | }, 1434 | "funding": [ 1435 | { 1436 | "url": "https://github.com/sebastianbergmann", 1437 | "type": "github" 1438 | } 1439 | ], 1440 | "time": "2022-02-14T08:28:10+00:00" 1441 | }, 1442 | { 1443 | "name": "sebastian/lines-of-code", 1444 | "version": "1.0.3", 1445 | "source": { 1446 | "type": "git", 1447 | "url": "https://github.com/sebastianbergmann/lines-of-code.git", 1448 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" 1449 | }, 1450 | "dist": { 1451 | "type": "zip", 1452 | "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1453 | "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", 1454 | "shasum": "" 1455 | }, 1456 | "require": { 1457 | "nikic/php-parser": "^4.6", 1458 | "php": ">=7.3" 1459 | }, 1460 | "require-dev": { 1461 | "phpunit/phpunit": "^9.3" 1462 | }, 1463 | "type": "library", 1464 | "extra": { 1465 | "branch-alias": { 1466 | "dev-master": "1.0-dev" 1467 | } 1468 | }, 1469 | "autoload": { 1470 | "classmap": [ 1471 | "src/" 1472 | ] 1473 | }, 1474 | "notification-url": "https://packagist.org/downloads/", 1475 | "license": [ 1476 | "BSD-3-Clause" 1477 | ], 1478 | "authors": [ 1479 | { 1480 | "name": "Sebastian Bergmann", 1481 | "email": "sebastian@phpunit.de", 1482 | "role": "lead" 1483 | } 1484 | ], 1485 | "description": "Library for counting the lines of code in PHP source code", 1486 | "homepage": "https://github.com/sebastianbergmann/lines-of-code", 1487 | "support": { 1488 | "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", 1489 | "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" 1490 | }, 1491 | "funding": [ 1492 | { 1493 | "url": "https://github.com/sebastianbergmann", 1494 | "type": "github" 1495 | } 1496 | ], 1497 | "time": "2020-11-28T06:42:11+00:00" 1498 | }, 1499 | { 1500 | "name": "sebastian/object-enumerator", 1501 | "version": "4.0.4", 1502 | "source": { 1503 | "type": "git", 1504 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 1505 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" 1506 | }, 1507 | "dist": { 1508 | "type": "zip", 1509 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", 1510 | "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", 1511 | "shasum": "" 1512 | }, 1513 | "require": { 1514 | "php": ">=7.3", 1515 | "sebastian/object-reflector": "^2.0", 1516 | "sebastian/recursion-context": "^4.0" 1517 | }, 1518 | "require-dev": { 1519 | "phpunit/phpunit": "^9.3" 1520 | }, 1521 | "type": "library", 1522 | "extra": { 1523 | "branch-alias": { 1524 | "dev-master": "4.0-dev" 1525 | } 1526 | }, 1527 | "autoload": { 1528 | "classmap": [ 1529 | "src/" 1530 | ] 1531 | }, 1532 | "notification-url": "https://packagist.org/downloads/", 1533 | "license": [ 1534 | "BSD-3-Clause" 1535 | ], 1536 | "authors": [ 1537 | { 1538 | "name": "Sebastian Bergmann", 1539 | "email": "sebastian@phpunit.de" 1540 | } 1541 | ], 1542 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 1543 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 1544 | "support": { 1545 | "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", 1546 | "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" 1547 | }, 1548 | "funding": [ 1549 | { 1550 | "url": "https://github.com/sebastianbergmann", 1551 | "type": "github" 1552 | } 1553 | ], 1554 | "time": "2020-10-26T13:12:34+00:00" 1555 | }, 1556 | { 1557 | "name": "sebastian/object-reflector", 1558 | "version": "2.0.4", 1559 | "source": { 1560 | "type": "git", 1561 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 1562 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" 1563 | }, 1564 | "dist": { 1565 | "type": "zip", 1566 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1567 | "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", 1568 | "shasum": "" 1569 | }, 1570 | "require": { 1571 | "php": ">=7.3" 1572 | }, 1573 | "require-dev": { 1574 | "phpunit/phpunit": "^9.3" 1575 | }, 1576 | "type": "library", 1577 | "extra": { 1578 | "branch-alias": { 1579 | "dev-master": "2.0-dev" 1580 | } 1581 | }, 1582 | "autoload": { 1583 | "classmap": [ 1584 | "src/" 1585 | ] 1586 | }, 1587 | "notification-url": "https://packagist.org/downloads/", 1588 | "license": [ 1589 | "BSD-3-Clause" 1590 | ], 1591 | "authors": [ 1592 | { 1593 | "name": "Sebastian Bergmann", 1594 | "email": "sebastian@phpunit.de" 1595 | } 1596 | ], 1597 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 1598 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 1599 | "support": { 1600 | "issues": "https://github.com/sebastianbergmann/object-reflector/issues", 1601 | "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" 1602 | }, 1603 | "funding": [ 1604 | { 1605 | "url": "https://github.com/sebastianbergmann", 1606 | "type": "github" 1607 | } 1608 | ], 1609 | "time": "2020-10-26T13:14:26+00:00" 1610 | }, 1611 | { 1612 | "name": "sebastian/recursion-context", 1613 | "version": "4.0.5", 1614 | "source": { 1615 | "type": "git", 1616 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1617 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1" 1618 | }, 1619 | "dist": { 1620 | "type": "zip", 1621 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1622 | "reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1", 1623 | "shasum": "" 1624 | }, 1625 | "require": { 1626 | "php": ">=7.3" 1627 | }, 1628 | "require-dev": { 1629 | "phpunit/phpunit": "^9.3" 1630 | }, 1631 | "type": "library", 1632 | "extra": { 1633 | "branch-alias": { 1634 | "dev-master": "4.0-dev" 1635 | } 1636 | }, 1637 | "autoload": { 1638 | "classmap": [ 1639 | "src/" 1640 | ] 1641 | }, 1642 | "notification-url": "https://packagist.org/downloads/", 1643 | "license": [ 1644 | "BSD-3-Clause" 1645 | ], 1646 | "authors": [ 1647 | { 1648 | "name": "Sebastian Bergmann", 1649 | "email": "sebastian@phpunit.de" 1650 | }, 1651 | { 1652 | "name": "Jeff Welch", 1653 | "email": "whatthejeff@gmail.com" 1654 | }, 1655 | { 1656 | "name": "Adam Harvey", 1657 | "email": "aharvey@php.net" 1658 | } 1659 | ], 1660 | "description": "Provides functionality to recursively process PHP variables", 1661 | "homepage": "https://github.com/sebastianbergmann/recursion-context", 1662 | "support": { 1663 | "issues": "https://github.com/sebastianbergmann/recursion-context/issues", 1664 | "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5" 1665 | }, 1666 | "funding": [ 1667 | { 1668 | "url": "https://github.com/sebastianbergmann", 1669 | "type": "github" 1670 | } 1671 | ], 1672 | "time": "2023-02-03T06:07:39+00:00" 1673 | }, 1674 | { 1675 | "name": "sebastian/resource-operations", 1676 | "version": "3.0.3", 1677 | "source": { 1678 | "type": "git", 1679 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1680 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" 1681 | }, 1682 | "dist": { 1683 | "type": "zip", 1684 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1685 | "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", 1686 | "shasum": "" 1687 | }, 1688 | "require": { 1689 | "php": ">=7.3" 1690 | }, 1691 | "require-dev": { 1692 | "phpunit/phpunit": "^9.0" 1693 | }, 1694 | "type": "library", 1695 | "extra": { 1696 | "branch-alias": { 1697 | "dev-master": "3.0-dev" 1698 | } 1699 | }, 1700 | "autoload": { 1701 | "classmap": [ 1702 | "src/" 1703 | ] 1704 | }, 1705 | "notification-url": "https://packagist.org/downloads/", 1706 | "license": [ 1707 | "BSD-3-Clause" 1708 | ], 1709 | "authors": [ 1710 | { 1711 | "name": "Sebastian Bergmann", 1712 | "email": "sebastian@phpunit.de" 1713 | } 1714 | ], 1715 | "description": "Provides a list of PHP built-in functions that operate on resources", 1716 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1717 | "support": { 1718 | "issues": "https://github.com/sebastianbergmann/resource-operations/issues", 1719 | "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" 1720 | }, 1721 | "funding": [ 1722 | { 1723 | "url": "https://github.com/sebastianbergmann", 1724 | "type": "github" 1725 | } 1726 | ], 1727 | "time": "2020-09-28T06:45:17+00:00" 1728 | }, 1729 | { 1730 | "name": "sebastian/type", 1731 | "version": "3.2.1", 1732 | "source": { 1733 | "type": "git", 1734 | "url": "https://github.com/sebastianbergmann/type.git", 1735 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7" 1736 | }, 1737 | "dist": { 1738 | "type": "zip", 1739 | "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 1740 | "reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7", 1741 | "shasum": "" 1742 | }, 1743 | "require": { 1744 | "php": ">=7.3" 1745 | }, 1746 | "require-dev": { 1747 | "phpunit/phpunit": "^9.5" 1748 | }, 1749 | "type": "library", 1750 | "extra": { 1751 | "branch-alias": { 1752 | "dev-master": "3.2-dev" 1753 | } 1754 | }, 1755 | "autoload": { 1756 | "classmap": [ 1757 | "src/" 1758 | ] 1759 | }, 1760 | "notification-url": "https://packagist.org/downloads/", 1761 | "license": [ 1762 | "BSD-3-Clause" 1763 | ], 1764 | "authors": [ 1765 | { 1766 | "name": "Sebastian Bergmann", 1767 | "email": "sebastian@phpunit.de", 1768 | "role": "lead" 1769 | } 1770 | ], 1771 | "description": "Collection of value objects that represent the types of the PHP type system", 1772 | "homepage": "https://github.com/sebastianbergmann/type", 1773 | "support": { 1774 | "issues": "https://github.com/sebastianbergmann/type/issues", 1775 | "source": "https://github.com/sebastianbergmann/type/tree/3.2.1" 1776 | }, 1777 | "funding": [ 1778 | { 1779 | "url": "https://github.com/sebastianbergmann", 1780 | "type": "github" 1781 | } 1782 | ], 1783 | "time": "2023-02-03T06:13:03+00:00" 1784 | }, 1785 | { 1786 | "name": "sebastian/version", 1787 | "version": "3.0.2", 1788 | "source": { 1789 | "type": "git", 1790 | "url": "https://github.com/sebastianbergmann/version.git", 1791 | "reference": "c6c1022351a901512170118436c764e473f6de8c" 1792 | }, 1793 | "dist": { 1794 | "type": "zip", 1795 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", 1796 | "reference": "c6c1022351a901512170118436c764e473f6de8c", 1797 | "shasum": "" 1798 | }, 1799 | "require": { 1800 | "php": ">=7.3" 1801 | }, 1802 | "type": "library", 1803 | "extra": { 1804 | "branch-alias": { 1805 | "dev-master": "3.0-dev" 1806 | } 1807 | }, 1808 | "autoload": { 1809 | "classmap": [ 1810 | "src/" 1811 | ] 1812 | }, 1813 | "notification-url": "https://packagist.org/downloads/", 1814 | "license": [ 1815 | "BSD-3-Clause" 1816 | ], 1817 | "authors": [ 1818 | { 1819 | "name": "Sebastian Bergmann", 1820 | "email": "sebastian@phpunit.de", 1821 | "role": "lead" 1822 | } 1823 | ], 1824 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1825 | "homepage": "https://github.com/sebastianbergmann/version", 1826 | "support": { 1827 | "issues": "https://github.com/sebastianbergmann/version/issues", 1828 | "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" 1829 | }, 1830 | "funding": [ 1831 | { 1832 | "url": "https://github.com/sebastianbergmann", 1833 | "type": "github" 1834 | } 1835 | ], 1836 | "time": "2020-09-28T06:39:44+00:00" 1837 | }, 1838 | { 1839 | "name": "symfony/polyfill-mbstring", 1840 | "version": "v1.27.0", 1841 | "source": { 1842 | "type": "git", 1843 | "url": "https://github.com/symfony/polyfill-mbstring.git", 1844 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" 1845 | }, 1846 | "dist": { 1847 | "type": "zip", 1848 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", 1849 | "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", 1850 | "shasum": "" 1851 | }, 1852 | "require": { 1853 | "php": ">=7.1" 1854 | }, 1855 | "provide": { 1856 | "ext-mbstring": "*" 1857 | }, 1858 | "suggest": { 1859 | "ext-mbstring": "For best performance" 1860 | }, 1861 | "type": "library", 1862 | "extra": { 1863 | "branch-alias": { 1864 | "dev-main": "1.27-dev" 1865 | }, 1866 | "thanks": { 1867 | "name": "symfony/polyfill", 1868 | "url": "https://github.com/symfony/polyfill" 1869 | } 1870 | }, 1871 | "autoload": { 1872 | "files": [ 1873 | "bootstrap.php" 1874 | ], 1875 | "psr-4": { 1876 | "Symfony\\Polyfill\\Mbstring\\": "" 1877 | } 1878 | }, 1879 | "notification-url": "https://packagist.org/downloads/", 1880 | "license": [ 1881 | "MIT" 1882 | ], 1883 | "authors": [ 1884 | { 1885 | "name": "Nicolas Grekas", 1886 | "email": "p@tchwork.com" 1887 | }, 1888 | { 1889 | "name": "Symfony Community", 1890 | "homepage": "https://symfony.com/contributors" 1891 | } 1892 | ], 1893 | "description": "Symfony polyfill for the Mbstring extension", 1894 | "homepage": "https://symfony.com", 1895 | "keywords": [ 1896 | "compatibility", 1897 | "mbstring", 1898 | "polyfill", 1899 | "portable", 1900 | "shim" 1901 | ], 1902 | "support": { 1903 | "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" 1904 | }, 1905 | "funding": [ 1906 | { 1907 | "url": "https://symfony.com/sponsor", 1908 | "type": "custom" 1909 | }, 1910 | { 1911 | "url": "https://github.com/fabpot", 1912 | "type": "github" 1913 | }, 1914 | { 1915 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 1916 | "type": "tidelift" 1917 | } 1918 | ], 1919 | "time": "2022-11-03T14:55:06+00:00" 1920 | }, 1921 | { 1922 | "name": "symfony/var-dumper", 1923 | "version": "v6.2.7", 1924 | "source": { 1925 | "type": "git", 1926 | "url": "https://github.com/symfony/var-dumper.git", 1927 | "reference": "cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e" 1928 | }, 1929 | "dist": { 1930 | "type": "zip", 1931 | "url": "https://api.github.com/repos/symfony/var-dumper/zipball/cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e", 1932 | "reference": "cf8d4ca1ddc1e3cc242375deb8fc23e54f5e2a1e", 1933 | "shasum": "" 1934 | }, 1935 | "require": { 1936 | "php": ">=8.1", 1937 | "symfony/polyfill-mbstring": "~1.0" 1938 | }, 1939 | "conflict": { 1940 | "phpunit/phpunit": "<5.4.3", 1941 | "symfony/console": "<5.4" 1942 | }, 1943 | "require-dev": { 1944 | "ext-iconv": "*", 1945 | "symfony/console": "^5.4|^6.0", 1946 | "symfony/process": "^5.4|^6.0", 1947 | "symfony/uid": "^5.4|^6.0", 1948 | "twig/twig": "^2.13|^3.0.4" 1949 | }, 1950 | "suggest": { 1951 | "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", 1952 | "ext-intl": "To show region name in time zone dump", 1953 | "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" 1954 | }, 1955 | "bin": [ 1956 | "Resources/bin/var-dump-server" 1957 | ], 1958 | "type": "library", 1959 | "autoload": { 1960 | "files": [ 1961 | "Resources/functions/dump.php" 1962 | ], 1963 | "psr-4": { 1964 | "Symfony\\Component\\VarDumper\\": "" 1965 | }, 1966 | "exclude-from-classmap": [ 1967 | "/Tests/" 1968 | ] 1969 | }, 1970 | "notification-url": "https://packagist.org/downloads/", 1971 | "license": [ 1972 | "MIT" 1973 | ], 1974 | "authors": [ 1975 | { 1976 | "name": "Nicolas Grekas", 1977 | "email": "p@tchwork.com" 1978 | }, 1979 | { 1980 | "name": "Symfony Community", 1981 | "homepage": "https://symfony.com/contributors" 1982 | } 1983 | ], 1984 | "description": "Provides mechanisms for walking through any arbitrary PHP variable", 1985 | "homepage": "https://symfony.com", 1986 | "keywords": [ 1987 | "debug", 1988 | "dump" 1989 | ], 1990 | "support": { 1991 | "source": "https://github.com/symfony/var-dumper/tree/v6.2.7" 1992 | }, 1993 | "funding": [ 1994 | { 1995 | "url": "https://symfony.com/sponsor", 1996 | "type": "custom" 1997 | }, 1998 | { 1999 | "url": "https://github.com/fabpot", 2000 | "type": "github" 2001 | }, 2002 | { 2003 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", 2004 | "type": "tidelift" 2005 | } 2006 | ], 2007 | "time": "2023-02-24T10:42:00+00:00" 2008 | }, 2009 | { 2010 | "name": "theseer/tokenizer", 2011 | "version": "1.2.1", 2012 | "source": { 2013 | "type": "git", 2014 | "url": "https://github.com/theseer/tokenizer.git", 2015 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" 2016 | }, 2017 | "dist": { 2018 | "type": "zip", 2019 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", 2020 | "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", 2021 | "shasum": "" 2022 | }, 2023 | "require": { 2024 | "ext-dom": "*", 2025 | "ext-tokenizer": "*", 2026 | "ext-xmlwriter": "*", 2027 | "php": "^7.2 || ^8.0" 2028 | }, 2029 | "type": "library", 2030 | "autoload": { 2031 | "classmap": [ 2032 | "src/" 2033 | ] 2034 | }, 2035 | "notification-url": "https://packagist.org/downloads/", 2036 | "license": [ 2037 | "BSD-3-Clause" 2038 | ], 2039 | "authors": [ 2040 | { 2041 | "name": "Arne Blankerts", 2042 | "email": "arne@blankerts.de", 2043 | "role": "Developer" 2044 | } 2045 | ], 2046 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2047 | "support": { 2048 | "issues": "https://github.com/theseer/tokenizer/issues", 2049 | "source": "https://github.com/theseer/tokenizer/tree/1.2.1" 2050 | }, 2051 | "funding": [ 2052 | { 2053 | "url": "https://github.com/theseer", 2054 | "type": "github" 2055 | } 2056 | ], 2057 | "time": "2021-07-28T10:34:58+00:00" 2058 | } 2059 | ], 2060 | "aliases": [], 2061 | "minimum-stability": "stable", 2062 | "stability-flags": [], 2063 | "prefer-stable": false, 2064 | "prefer-lowest": false, 2065 | "platform": { 2066 | "php": "^8.1", 2067 | "ext-inotify": "*" 2068 | }, 2069 | "platform-dev": [], 2070 | "plugin-api-version": "2.3.0" 2071 | } 2072 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 8 3 | paths: ['src', 'tests'] 4 | checkMissingIterableValueType: false 5 | checkGenericClassInNonGenericObjectType: false 6 | ignoreErrors: 7 | - '#Attribute class JetBrains\\PhpStorm.*#' 8 | - '#Used function Swoole\\Coroutine\\run not found#' 9 | - '#Function Swoole\\Coroutine\\run not found.*#' -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/Event.php: -------------------------------------------------------------------------------- 1 | __eventTraitConstructor(); 43 | 44 | $this->maskItemCreated = Event::ON_CREATE_HIGH->value; 45 | $this->maskItemDeleted = Event::ON_DELETE_HIGH->value; 46 | } 47 | 48 | /** 49 | * Get inotify file descriptor 50 | * 51 | * @return mixed 52 | */ 53 | protected function getInotifyFD(): mixed 54 | { 55 | if (!isset($this->inotifyFD)) { 56 | $this->inotifyFD = inotify_init(); 57 | } 58 | 59 | return $this->inotifyFD; 60 | } 61 | 62 | /** 63 | * Handles directory creation/deletion on the fly 64 | * 65 | * @param array $inotifyEvent 66 | * @return void 67 | */ 68 | protected function inotifyPerformAdditionalOperations(array $inotifyEvent): void 69 | { 70 | // Handle directory creation 71 | $eventInfo = new EventInfo($inotifyEvent, $this->watchedItems[$inotifyEvent['wd']]); 72 | 73 | if ($inotifyEvent['mask'] == $this->maskItemCreated) { 74 | // Register this path also if it's directory 75 | if ($eventInfo->getWatchedItem()->isDir()) { 76 | $this->inotifyWatchPathRecursively($eventInfo->getWatchedItem()->getFullPath()); 77 | } 78 | 79 | return; 80 | } 81 | 82 | // Handle file/directory deletion 83 | if ($eventInfo->getWatchedItem()->isDir()) { 84 | $this->inotifyRemovePathWatch($eventInfo); 85 | } 86 | } 87 | 88 | /** 89 | * Register directory/file to inotify watcher 90 | * Loops through directory recursively and register all it's subdirectories as well 91 | * 92 | * @param string $path 93 | * @return void 94 | */ 95 | protected function inotifyWatchPathRecursively(string $path): void 96 | { 97 | if (is_dir($path)) { 98 | $iterator = new RecursiveDirectoryIterator($path); 99 | 100 | /** 101 | * Loop through files 102 | * 103 | * @var SplFileInfo $file 104 | */ 105 | foreach (new RecursiveIteratorIterator($iterator) as $file) { 106 | $realPath = $file->getRealPath(); 107 | if (is_string($realPath) && $file->isDir() && !in_array($realPath, $this->watchedItems)) { 108 | $this->inotifyWatchPath($realPath); 109 | } 110 | } 111 | 112 | return; 113 | } 114 | 115 | // Register file watch 116 | $this->inotifyWatchPath($path); 117 | } 118 | 119 | /** 120 | * Register directory/file to inotify watcher 121 | * 122 | * @param string $path 123 | * @return void 124 | */ 125 | protected function inotifyWatchPath(string $path): void 126 | { 127 | $descriptor = inotify_add_watch( 128 | $this->getInotifyFD(), 129 | $path, 130 | Event::ON_ALL_EVENTS->value 131 | ); 132 | 133 | $this->watchedItems[$descriptor] = $path; 134 | } 135 | 136 | /** 137 | * Stop watching file/directory 138 | * 139 | * @param EventInfo $eventInfo 140 | * @return void 141 | */ 142 | protected function inotifyRemovePathWatch(EventInfo $eventInfo): void 143 | { 144 | $descriptor = $eventInfo->getWatchDescriptor(); 145 | if ($eventInfo->getWatchedItem()->getFullPath() != $this->watchedItems[$descriptor]) { 146 | return; 147 | } 148 | 149 | // Stop watching event 150 | @inotify_rm_watch($this->getInotifyFD(), $descriptor); 151 | 152 | // Stop tracking descriptor 153 | unset($this->watchedItems[$descriptor]); 154 | } 155 | 156 | /** 157 | * Trigger an event 158 | * 159 | * @param array $inotifyEvent 160 | * @return void 161 | */ 162 | protected function fireEvent(array $inotifyEvent): void 163 | { 164 | $shouldFireEvent = array_key_exists($inotifyEvent['mask'], self::$constants); 165 | 166 | if ($shouldFireEvent) { 167 | // Make sure that the inotify fired event file name does not contain unneeded chars 168 | foreach ($this->fileShouldNotEndWith as $char) { 169 | if (str_ends_with($inotifyEvent['name'], $char)) { 170 | return; 171 | } 172 | } 173 | 174 | // Make sure that the event has registered items 175 | if ($this->willWatchAny) { 176 | $shouldFireEvent = array_key_exists($inotifyEvent['wd'], $this->watchedItems); 177 | } 178 | 179 | // Handle extension condition 180 | if ($shouldFireEvent && !empty($this->extensions)) { 181 | $expExt = explode('.', $inotifyEvent['name']); 182 | $shouldFireEvent = in_array(end($expExt), $this->extensions); 183 | } 184 | 185 | // Fire event if conditions are met 186 | if ($shouldFireEvent) { 187 | $eventInfo = new EventInfo($inotifyEvent, $this->watchedItems[$inotifyEvent['wd']]); 188 | 189 | foreach ($this->ignoredPaths as $ignoredPath) { 190 | $ignoredPath = str_replace("@", '\@', $ignoredPath); 191 | if (1 === preg_match("@$ignoredPath@", $eventInfo->getWatchedItem()->getFullPath())) { 192 | return; 193 | } 194 | } 195 | 196 | $eventMask = $this->willWatchAny 197 | ? Event::ON_ALL_EVENTS->value 198 | : $eventInfo->getMask()->value; 199 | 200 | $this->eventEmitter->emit($eventMask, [$eventInfo]); 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * Returns list of files currently being watched 207 | * 208 | * @return WatchedItem[] 209 | */ 210 | public function getWatchedItems(): array 211 | { 212 | return $this->watchedItems; 213 | } 214 | 215 | /** 216 | * Start watching file system changes 217 | * 218 | * @return void 219 | */ 220 | public function watch(): void 221 | { 222 | // Register paths 223 | foreach ($this->paths as $path) { 224 | $this->inotifyWatchPathRecursively($path); 225 | } 226 | 227 | // Set up a new event listener for inotify read events 228 | SwooleEvent::add($this->getInotifyFD(), function () { 229 | $inotifyEvents = inotify_read($this->getInotifyFD()); 230 | 231 | // IF WE ARE LISTENING TO 'ON_ALL_EVENTS' 232 | if ($this->willWatchAny) { 233 | foreach ($inotifyEvents as $inotifyEvent) { 234 | $this->fireEvent($inotifyEvent); 235 | 236 | $this->inotifyPerformAdditionalOperations($inotifyEvent); 237 | } 238 | 239 | return; 240 | } 241 | 242 | // INDIVIDUAL LISTENERS 243 | foreach ($inotifyEvents as $inotifyEvent) { 244 | // var_export($inotifyEvent); 245 | // Make sure that we support this event 246 | if (in_array($inotifyEvent['mask'], $this->watchedMasks)) { 247 | $this->fireEvent($inotifyEvent); 248 | 249 | $this->inotifyPerformAdditionalOperations($inotifyEvent); 250 | } 251 | } 252 | 253 | }); 254 | 255 | // Set to monitor and listen for read events for the given $fd 256 | SwooleEvent::set( 257 | $this->getInotifyFD(), 258 | null, 259 | null, 260 | SWOOLE_EVENT_READ 261 | ); 262 | } 263 | 264 | /** 265 | * Add file extension filter 266 | * 267 | * @param string|array $extension 268 | * @return $this 269 | */ 270 | public function addExtension(string|array $extension): Watcher 271 | { 272 | if (is_array($extension)) { 273 | $this->extensions = array_merge($this->extensions, $extension); 274 | return $this; 275 | } 276 | 277 | $this->extensions[] = $extension; 278 | return $this; 279 | } 280 | 281 | /** 282 | * Add path to watch 283 | * 284 | * @param string|array $path 285 | * @return $this 286 | */ 287 | public function addPath(string|array $path): Watcher 288 | { 289 | if (is_array($path)) { 290 | $this->paths = array_merge($this->paths, $path); 291 | return $this; 292 | } 293 | 294 | $this->paths[] = $path; 295 | return $this; 296 | } 297 | 298 | /** 299 | * Add additional filter 300 | * 301 | * @param string[] $characters 302 | * @param bool $clearPreviousEntry This will overwrite previous entry, including built-in entries 303 | * @return $this 304 | */ 305 | public function fileShouldNotEndWith(array $characters, bool $clearPreviousEntry = false): Watcher 306 | { 307 | $clearPreviousEntry 308 | ? $this->fileShouldNotEndWith = $characters 309 | : $this->fileShouldNotEndWith = array_merge($this->fileShouldNotEndWith, $characters); 310 | 311 | return $this; 312 | } 313 | 314 | /** 315 | * Ignore event from being fired if path matches given ones 316 | * 317 | * @param array|string $path 318 | * @return $this 319 | */ 320 | public function ignore(array|string $path): Watcher 321 | { 322 | if (is_string($path)) { 323 | $this->ignoredPaths[] = $path; 324 | return $this; 325 | } 326 | 327 | $this->ignoredPaths = array_merge($this->ignoredPaths, $path); 328 | return $this; 329 | } 330 | 331 | /** 332 | * Proxy of Watcher::watch() 333 | * 334 | * @return void 335 | * @see Watcher::watch() 336 | */ 337 | public function start(): void 338 | { 339 | $this->watch(); 340 | } 341 | 342 | /** 343 | * Stop watching 344 | * 345 | * @return void 346 | */ 347 | public function stop(): void 348 | { 349 | if (isset($this->inotifyFD)) { 350 | // Ask Swoole to remote watcher on this FD 351 | SwooleEvent::del($this->inotifyFD); 352 | // Close inotify FD resource 353 | fclose($this->inotifyFD); 354 | // Delete the var content 355 | unset($this->inotifyFD); 356 | } 357 | } 358 | } -------------------------------------------------------------------------------- /src/Watching/EventInfo.php: -------------------------------------------------------------------------------- 1 | watchedItem = new WatchedItem($path, $this); 22 | $this->eventMask = $this->event['mask']; 23 | $this->eventInfo = Watcher::$constants[$event['mask']]; 24 | } 25 | 26 | public function getMask(): Event 27 | { 28 | return Event::from($this->eventMask); 29 | } 30 | 31 | public function getName(): string 32 | { 33 | return $this->eventInfo[0]; 34 | } 35 | 36 | public function getDesc(): string 37 | { 38 | return $this->eventInfo[1]; 39 | } 40 | 41 | /** 42 | * @return array 43 | */ 44 | public function getEvent(): array 45 | { 46 | return $this->event; 47 | } 48 | 49 | #[Pure] public function getWatchDescriptor(): int 50 | { 51 | return $this->getEvent()['wd']; 52 | } 53 | 54 | /** 55 | * @return WatchedItem 56 | */ 57 | public function getWatchedItem(): WatchedItem 58 | { 59 | return $this->watchedItem; 60 | } 61 | } -------------------------------------------------------------------------------- /src/Watching/EventTrait.php: -------------------------------------------------------------------------------- 1 | ['UNKNOWN', 'Unknown code.'], 18 | 1 => ['ON_ACCESS', 'File was accessed (read)'], 19 | 2 => ['ON_MODIFY', 'File was modified'], 20 | 4 => ['ON_ATTRIB', 'Metadata changed (e.g. permissions, mtime, etc.)'], 21 | 8 => ['ON_CLOSE_WRITE', 'File opened for writing was closed'], 22 | 16 => ['ON_CLOSE_NOWRITE', 'File not opened for writing was closed'], 23 | 32 => ['ON_OPEN', 'File was opened'], 24 | 128 => ['ON_MOVED_TO', 'File moved into watched directory'], 25 | 64 => ['ON_MOVED_FROM', 'File moved out of watched directory'], 26 | 256 => ['ON_CREATE', 'File or directory created in watched directory'], 27 | 512 => ['ON_DELETE', 'File or directory deleted in watched directory'], 28 | 1024 => ['ON_DELETE_SELF', 'Watched file or directory was deleted'], 29 | 2048 => ['ON_MOVE_SELF', 'Watch file or directory was moved'], 30 | 24 => ['ON_CLOSE', 'Equals to ON_CLOSE_WRITE | ON_CLOSE_NOWRITE'], 31 | 192 => ['ON_MOVE', 'Equals to ON_MOVED_FROM | ON_MOVED_TO'], 32 | 4095 => ['ON_ALL_EVENTS', 'Bitmask of all the above constants'], 33 | 8192 => ['ON_UNMOUNT', 'File system containing watched object was unmounted'], 34 | 16384 => ['ON_Q_OVERFLOW', 'Event queue overflowed (wd is -1 for this event)'], 35 | 32768 => ['ON_IGNORED', 'Watch was removed (explicitly by inotify_rm_watch() or because file was removed or filesystem unmounted'], 36 | 1073741824 => ['ON_ISDIR', 'Subject of this event is a directory'], 37 | 1073741840 => ['ON_CLOSE_NOWRITE', 'High-bit: File not opened for writing was closed'], 38 | 1073741856 => ['ON_OPEN_HIGH', 'High-bit: File was opened'], 39 | 1073742080 => ['ON_CREATE_HIGH', 'High-bit: File or directory created in watched directory'], 40 | 1073742336 => ['ON_DELETE_HIGH', 'High-bit: File or directory deleted in watched directory'], 41 | 16777216 => ['ON_ONLYDIR', 'Only watch pathname if it is a directory (Since Linux 2.6.15)'], 42 | 33554432 => ['ON_DONT_FOLLOW', 'Do not dereference pathname if it is a symlink (Since Linux 2.6.15)'], 43 | 536870912 => ['ON_MASK_ADD', 'Add events to watch mask for this pathname if it already exists (instead of replacing mask).'], 44 | 2147483648 => ['ON_ONESHOT', 'Monitor pathname for one event, then remove from watch list.'], 45 | ]; 46 | 47 | 48 | #[Pure] public function __construct() 49 | { 50 | $this->eventEmitter = new EventEmitter(); 51 | } 52 | 53 | public function on(Event $event, callable $handler): static 54 | { 55 | if ($event === Event::ON_ALL_EVENTS) { 56 | $this->willWatchAny = true; 57 | } 58 | 59 | $this->watchedMasks[] = $event->value; 60 | $this->eventEmitter->on($event->value, $handler); 61 | return $this; 62 | } 63 | 64 | public function once(Event $event, callable $handler): static 65 | { 66 | $this->watchedMasks[] = $event->value; 67 | $this->eventEmitter->once($event->value, $handler); 68 | return $this; 69 | } 70 | 71 | protected function emit(Event $event, array $data): void 72 | { 73 | $this->eventEmitter->emit($event->value, $data); 74 | } 75 | 76 | /** 77 | * @param callable $listener 78 | * @param bool $fireOnce Indicates that this event should only be listened once 79 | * @return static 80 | */ 81 | public function onAny(callable $listener, bool $fireOnce = false): static 82 | { 83 | return $this->listenToHelperEvent( 84 | Event::ON_ALL_EVENTS, 85 | $listener, 86 | $fireOnce 87 | ); 88 | } 89 | 90 | /** 91 | * Listen to change/update on provided paths 92 | * 93 | * @param callable $listener 94 | * @param bool $fireOnce Indicates that this event should only be listened once 95 | * @return static 96 | */ 97 | public function onChange(callable $listener, bool $fireOnce = false): static 98 | { 99 | return $this->listenToHelperEvent( 100 | Event::ON_CLOSE_WRITE, 101 | $listener, 102 | $fireOnce 103 | ); 104 | } 105 | 106 | /** 107 | * Listen to create event on provided paths 108 | * 109 | * @param callable $listener 110 | * @param bool $fireOnce Indicates that this event should only be listened once 111 | * @return static 112 | */ 113 | public function onCreate(callable $listener, bool $fireOnce = false): static 114 | { 115 | // Directory Creation 116 | $this->listenToHelperEvent( 117 | Event::ON_CREATE_HIGH, 118 | $listener, 119 | $fireOnce 120 | ); 121 | 122 | // File Creation 123 | return $this->listenToHelperEvent( 124 | Event::ON_CREATE, 125 | $listener, 126 | $fireOnce 127 | ); 128 | } 129 | 130 | /** 131 | * Listen to move event on provided paths 132 | * 133 | * @param callable $listener 134 | * @param bool $fireOnce Indicates that this event should only be listened once 135 | * @return static 136 | */ 137 | public function onMove(callable $listener, bool $fireOnce = false): static 138 | { 139 | return $this->listenToHelperEvent( 140 | Event::ON_MOVE, 141 | $listener, 142 | $fireOnce 143 | ); 144 | } 145 | 146 | /** 147 | * Listen to delete on provided paths 148 | * 149 | * @param callable $listener 150 | * @param bool $fireOnce Indicates that this event should only be listened once 151 | * @return static 152 | */ 153 | public function onDelete(callable $listener, bool $fireOnce = false): static 154 | { 155 | // Delete Directory 156 | $this->listenToHelperEvent( 157 | Event::ON_DELETE_HIGH, 158 | $listener, 159 | $fireOnce 160 | ); 161 | 162 | // Delete File 163 | return $this->listenToHelperEvent( 164 | Event::ON_DELETE, 165 | $listener, 166 | $fireOnce 167 | ); 168 | } 169 | 170 | protected function listenToHelperEvent(Event $event, callable $listener, bool $fireOnce = false): static 171 | { 172 | if ($event !== Event::ON_ALL_EVENTS) { 173 | $this->watchedMasks[] = $event->value; 174 | } 175 | 176 | $fireOnce 177 | ? $this->once($event, $listener) 178 | : $this->on($event, $listener); 179 | 180 | return $this; 181 | } 182 | } -------------------------------------------------------------------------------- /src/Watching/WatchedItem.php: -------------------------------------------------------------------------------- 1 | path)) { 26 | $this->fullPath = "$this->path/{$this->eventInfo->getEvent()['name']}"; 27 | } else { // This is when a file is being watched 28 | $this->fullPath = $this->path; 29 | } 30 | 31 | $this->isFile = is_file($this->fullPath); 32 | $dirName = pathinfo($this->fullPath)['dirname'] ?? null; 33 | 34 | if (!$dirName) { 35 | throw new \RuntimeException('Something went wrong, failed to acquire file info'); 36 | } 37 | 38 | $this->dirName = $dirName; 39 | } 40 | 41 | /** 42 | * @return string 43 | */ 44 | public function getPath(): string 45 | { 46 | return $this->path; 47 | } 48 | 49 | #[Pure] public function getFullPath(): string 50 | { 51 | return $this->fullPath; 52 | } 53 | 54 | /** 55 | * @return mixed|string 56 | */ 57 | public function getDirName(): mixed 58 | { 59 | return $this->dirName; 60 | } 61 | 62 | /** 63 | * @return bool 64 | */ 65 | public function isDir(): bool 66 | { 67 | return !$this->isFile; 68 | } 69 | 70 | /** 71 | * @return bool 72 | */ 73 | public function isFile(): bool 74 | { 75 | return $this->isFile; 76 | } 77 | } -------------------------------------------------------------------------------- /tests/TWatcher.php: -------------------------------------------------------------------------------- 1 | baitDir)) { 24 | mkdir($this->baitDir); 25 | } 26 | } 27 | 28 | protected function tearDown(): void 29 | { 30 | if (file_exists($this->baitDir)) { 31 | rmdir($this->baitDir); 32 | } 33 | 34 | parent::tearDown(); 35 | } 36 | 37 | public function testFileAndDirectoryCreation(): void 38 | { 39 | run(function (): void { 40 | $fileToCreate = $this->baitDir('test.txt'); 41 | $dirToCreate = $this->baitDir('test'); 42 | 43 | $watcher = TWatcher::create(); 44 | 45 | $watcher 46 | ->addPath($this->baitDir) 47 | ->onCreate(function (EventInfo $eventInfo) use ($fileToCreate, $dirToCreate, &$watcher) { 48 | static $calls = 0; 49 | $calls += 1; 50 | 51 | if ($eventInfo->getWatchedItem()->isFile()) { 52 | self::assertSame($fileToCreate, $eventInfo->getWatchedItem()->getFullPath()); 53 | } 54 | 55 | if ($eventInfo->getWatchedItem()->isDir()) { 56 | self::assertSame($dirToCreate, $eventInfo->getWatchedItem()->getFullPath()); 57 | } 58 | 59 | if ($calls == 2) { 60 | $watcher->stop(); 61 | } 62 | }) 63 | ->start(); 64 | 65 | Coroutine::sleep(0.1); 66 | touch($fileToCreate); 67 | mkdir($dirToCreate); 68 | 69 | unlink($fileToCreate); 70 | rmdir($dirToCreate); 71 | }); 72 | } 73 | 74 | public function testFileAndDirectoryDeletion(): void 75 | { 76 | run(function (): void { 77 | $fileToDelete = $this->baitDir('testy.txt'); 78 | $dirToDelete = $this->baitDir('testy'); 79 | 80 | touch($fileToDelete); 81 | mkdir($dirToDelete); 82 | 83 | $watcher = TWatcher::create(); 84 | 85 | $watcher 86 | ->addPath($this->baitDir) 87 | ->onDelete(function (EventInfo $eventInfo) use ($fileToDelete, $dirToDelete, &$watcher) { 88 | static $calls = 0; 89 | $calls += 1; 90 | 91 | if ($calls == 1) { 92 | self::assertSame($fileToDelete, $eventInfo->getWatchedItem()->getFullPath()); 93 | } 94 | 95 | if ($calls == 2) { 96 | self::assertSame($dirToDelete, $eventInfo->getWatchedItem()->getFullPath()); 97 | $watcher->stop(); 98 | } 99 | }) 100 | ->start(); 101 | 102 | unlink($fileToDelete); 103 | rmdir($dirToDelete); 104 | }); 105 | } 106 | 107 | public function testFileChange(): void 108 | { 109 | run(function (): void { 110 | $fileToUpdate = $this->baitDir('test.txt'); 111 | touch($fileToUpdate); 112 | 113 | $watcher = TWatcher::create(); 114 | 115 | $watcher 116 | ->addPath($this->baitDir) 117 | ->onChange(function (EventInfo $eventInfo) use ($fileToUpdate, &$watcher) { 118 | self::assertSame($fileToUpdate, $eventInfo->getWatchedItem()->getFullPath()); 119 | $watcher->stop(); 120 | 121 | unlink($fileToUpdate); 122 | }) 123 | ->start(); 124 | 125 | file_put_contents($fileToUpdate, uniqid()); 126 | }); 127 | } 128 | 129 | public function testDirWithSpecialChars(): void 130 | { 131 | run(function (): void { 132 | $watcher = TWatcher::create(); 133 | $baitDir = $this->baitDir('@hello'); 134 | $baitFile = $baitDir . '/world.txt'; 135 | 136 | mkdir($baitDir); 137 | self::assertDirectoryExists($baitDir); 138 | 139 | $watcher 140 | ->addPath($baitDir) 141 | ->onCreate(function (EventInfo $eventInfo) use ($baitFile, $watcher): void { 142 | if ($eventInfo->getWatchedItem()->isFile()) { 143 | self::assertSame($baitFile, $eventInfo->getWatchedItem()->getFullPath()); 144 | } 145 | 146 | $watcher->stop(); 147 | }) 148 | ->start(); 149 | 150 | touch($baitFile); 151 | Coroutine::sleep(0.1); 152 | unlink($baitFile); 153 | 154 | rmdir($baitDir); 155 | self::assertDirectoryDoesNotExist($baitDir); 156 | }); 157 | } 158 | 159 | public function testCreateChangeDeleteOnTheFly(): void 160 | { 161 | run(function () { 162 | $watcher = TWatcher::create(); 163 | $baitDir = $this->baitDir('dir-on-the-fly'); 164 | $baitFile = $baitDir . '/bait.txt'; 165 | 166 | $watcher 167 | ->addPath($this->baitDir) 168 | ->onCreate(function (EventInfo $eventInfo) use ($baitDir, $baitFile): void { 169 | if ($eventInfo->getWatchedItem()->isDir()) { 170 | self::assertSame($baitDir, $eventInfo->getWatchedItem()->getFullPath()); 171 | } 172 | 173 | if ($eventInfo->getWatchedItem()->isFile()) { 174 | self::assertSame($baitFile, $eventInfo->getWatchedItem()->getFullPath()); 175 | } 176 | }) 177 | ->onChange(function (EventInfo $eventInfo) use ($baitFile): void { 178 | self::assertSame($baitFile, $eventInfo->getWatchedItem()->getFullPath()); 179 | }) 180 | ->onDelete(function (EventInfo $eventInfo) use (&$watcher, $baitDir, $baitFile) { 181 | static $calls = 0; 182 | 183 | $calls += 1; 184 | 185 | if ($calls == 1) { 186 | self::assertSame($baitFile, $eventInfo->getWatchedItem()->getFullPath()); 187 | } 188 | 189 | if ($calls == 2) { 190 | self::assertSame($baitDir, $eventInfo->getWatchedItem()->getFullPath()); 191 | $watcher->stop(); 192 | } 193 | }) 194 | ->start(); 195 | 196 | Coroutine::sleep(0.1); 197 | mkdir($baitDir); 198 | Coroutine::sleep(0.1); 199 | touch($baitFile); 200 | 201 | Coroutine::sleep(0.1); 202 | file_put_contents($baitFile, uniqid()); 203 | 204 | Coroutine::sleep(0.1); 205 | unlink($baitFile); 206 | rmdir($baitDir); 207 | }); 208 | } 209 | 210 | 211 | private function baitDir(string $path): string 212 | { 213 | return "$this->baitDir/$path"; 214 | } 215 | } 216 | --------------------------------------------------------------------------------