diff --git a/Dockerfile b/Dockerfile index ce762c3..0c8b88e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,13 @@ -FROM php:8.3-cli +FROM danog/madelineproto:latest -RUN apt-get update && apt-get upgrade -y -RUN true \ - # Install main extension - && apt-get install procps git zip vim libzip-dev libgmp-dev libuv1-dev libssl-dev libnghttp2-dev libffi-dev libicu-dev libonig-dev libxml2-dev libpng-dev -y \ - && docker-php-ext-install -j$(nproc) sockets bcmath mysqli pdo_mysql pcntl ffi intl gmp zip gd \ - # Install additional extension - && mkdir -p /usr/src/php/ext/ && cd /usr/src/php/ext/ \ - && pecl bundle uv && pecl bundle igbinary \ - && docker-php-ext-install -j$(nproc) uv igbinary \ - # Install PrimeModule for AuthKey generation speedup - && git clone https://github.com/danog/PrimeModule-ext \ - && cd PrimeModule-ext && make -j$(nproc) \ - && make install \ - && cd ../ \ - && rm -rf PrimeModule-ext/ \ - # Install composer - && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \ - # Cleanup - && docker-php-source delete \ - && apt-get autoremove --purge -y && apt-get autoclean -y && apt-get clean -y \ - && rm -rf /usr/src +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer COPY --from=ghcr.io/ufoscout/docker-compose-wait:latest /wait /usr/local/bin/docker-compose-wait -ADD docker/php/conf.d/. "$PHP_INI_DIR/conf.d/" +RUN echo 1.0.0 > /tas_version EXPOSE 9503 -ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file +ENV UV_USE_IO_URING=0 + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/Dockerfile-dev b/Dockerfile-dev index edad447..5e129db 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,12 +1,16 @@ FROM xtrime/telegram-api-server:latest -ADD docker/php/conf.d/. "$PHP_INI_DIR/conf.d/" +ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ -RUN pecl install xdebug \ - && echo "" >> "$PHP_INI_DIR/conf.d/xdebug.ini" \ - && echo "zend_extension=xdebug.so" >> "$PHP_INI_DIR/conf.d/xdebug.ini" +RUN echo "opcache.jit=disable" >> "$PHP_INI_DIR/conf.d/xdebug.ini" + +RUN chmod +x /usr/local/bin/install-php-extensions && \ + install-php-extensions xdebug && \ + rm /usr/local/bin/install-php-extensions + +ADD dev/. "$PHP_INI_DIR/conf.d/" EXPOSE 9503 EXPOSE 9003 -ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["./entrypoint.sh"] diff --git a/bootstrap.php b/bootstrap.php index 0705a7d..e9837cc 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -1,5 +1,9 @@ emergency($message, $context); } } + +EventLoop::setErrorHandler(function (\Throwable $e) { + if ($e instanceof UnhandledFutureError) { + $e = $e->getPrevious(); + } + if ($e instanceof SecurityException || $e instanceof SignalException || $e instanceof SqlException) { + throw $e; + } + if (str_starts_with($e->getMessage(), 'Could not connect to DC ')) { + throw $e; + } + emergency((string) $e); +}); \ No newline at end of file diff --git a/composer.json b/composer.json index 3a6da8b..ca916f8 100644 --- a/composer.json +++ b/composer.json @@ -24,19 +24,22 @@ } ], "require": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "ext-mbstring": "*", - "amphp/http-server": "^v3", - "amphp/http": "^v2", + "amphp/http-server": "^3.3.1", + "amphp/http": "^2.1.1", "amphp/http-server-router": "^2", "amphp/http-server-form-parser": "^v2", - "amphp/websocket-server": "^v3", + "amphp/websocket-server": "^3.0.1", "amphp/websocket-client": "^v2", - "vlucas/phpdotenv": "^4", + "vlucas/phpdotenv": "^4.3", "danog/madelineproto": "dev-v8_fix_cleanup", "amphp/dns": "2.x-dev" }, + "require-dev": { + "amphp/php-cs-fixer-config": "^2.0.1" + }, "suggest": { "ext-pcntl": "Install pcintl for propper signal handling and healthcheck (enabled in .env)" }, @@ -57,5 +60,8 @@ "allow-plugins": { "symfony/thanks": false } + }, + "scripts": { + "cs-fix": "PHP_CS_FIXER_IGNORE_ENV=1 php -d pcre.jit=0 vendor/bin/php-cs-fixer fix -v" } } diff --git a/composer.lock b/composer.lock index db43222..cc80baa 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "09b26d1017fd85d16a9cb71fc6048cdf", + "content-hash": "b9e9ba3184bce93fe3654528621101f4", "packages": [ { "name": "amphp/amp", @@ -1274,16 +1274,16 @@ }, { "name": "amphp/pipeline", - "version": "v1.2.0", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/amphp/pipeline.git", - "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b" + "reference": "66c095673aa5b6e689e63b52d19e577459129ab3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/pipeline/zipball/f1c2ce35d27ae86ead018adb803eccca7421dd9b", - "reference": "f1c2ce35d27ae86ead018adb803eccca7421dd9b", + "url": "https://api.github.com/repos/amphp/pipeline/zipball/66c095673aa5b6e689e63b52d19e577459129ab3", + "reference": "66c095673aa5b6e689e63b52d19e577459129ab3", "shasum": "" }, "require": { @@ -1329,7 +1329,7 @@ ], "support": { "issues": "https://github.com/amphp/pipeline/issues", - "source": "https://github.com/amphp/pipeline/tree/v1.2.0" + "source": "https://github.com/amphp/pipeline/tree/v1.2.1" }, "funding": [ { @@ -1337,7 +1337,7 @@ "type": "github" } ], - "time": "2024-03-10T14:48:16+00:00" + "time": "2024-07-04T00:56:47+00:00" }, { "name": "amphp/postgres", @@ -2646,12 +2646,12 @@ "source": { "type": "git", "url": "https://github.com/danog/MadelineProto.git", - "reference": "d5c1634ecfac473ce91f681c905a812a6ee7b303" + "reference": "ddb2bb5a2358cc62cb8d0f847710e2cccb106390" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danog/MadelineProto/zipball/d5c1634ecfac473ce91f681c905a812a6ee7b303", - "reference": "d5c1634ecfac473ce91f681c905a812a6ee7b303", + "url": "https://api.github.com/repos/danog/MadelineProto/zipball/ddb2bb5a2358cc62cb8d0f847710e2cccb106390", + "reference": "ddb2bb5a2358cc62cb8d0f847710e2cccb106390", "shasum": "" }, "require": { @@ -2766,7 +2766,7 @@ "type": "github" } ], - "time": "2024-06-13T15:22:29+00:00" + "time": "2024-07-10T14:36:36+00:00" }, { "name": "danog/primemodule", @@ -3402,16 +3402,16 @@ }, { "name": "monolog/monolog", - "version": "3.6.0", + "version": "3.7.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654" + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", - "reference": "4b18b21a5527a3d5ffdac2fd35d3ab25a9597654", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", + "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", "shasum": "" }, "require": { @@ -3487,7 +3487,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.6.0" + "source": "https://github.com/Seldaek/monolog/tree/3.7.0" }, "funding": [ { @@ -3499,7 +3499,7 @@ "type": "tidelift" } ], - "time": "2024-04-12T21:02:21+00:00" + "time": "2024-06-28T09:40:51+00:00" }, { "name": "nikic/fast-route", @@ -3553,16 +3553,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.2", + "version": "v5.1.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", - "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1", + "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1", "shasum": "" }, "require": { @@ -3573,7 +3573,7 @@ }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -3605,30 +3605,30 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0" }, - "time": "2024-03-05T20:51:40+00:00" + "time": "2024-07-01T20:03:41+00:00" }, { "name": "paragonie/constant_time_encoding", - "version": "v2.7.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/paragonie/constant_time_encoding.git", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105" + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/52a0d99e69f56b9ec27ace92ba56897fe6993105", - "reference": "52a0d99e69f56b9ec27ace92ba56897fe6993105", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/df1e7fde177501eee2037dd159cf04f5f301a512", + "reference": "df1e7fde177501eee2037dd159cf04f5f301a512", "shasum": "" }, "require": { - "php": "^7|^8" + "php": "^8" }, "require-dev": { - "phpunit/phpunit": "^6|^7|^8|^9", - "vimeo/psalm": "^1|^2|^3|^4" + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4|^5" }, "type": "library", "autoload": { @@ -3674,7 +3674,7 @@ "issues": "https://github.com/paragonie/constant_time_encoding/issues", "source": "https://github.com/paragonie/constant_time_encoding" }, - "time": "2024-05-08T12:18:48+00:00" + "time": "2024-05-08T12:36:18+00:00" }, { "name": "paragonie/random_compat", @@ -3803,20 +3803,20 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.37", + "version": "3.0.39", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8" + "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cfa2013d0f68c062055180dd4328cc8b9d1f30b8", - "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/211ebc399c6e73c225a018435fe5ae209d1d1485", + "reference": "211ebc399c6e73c225a018435fe5ae209d1d1485", "shasum": "" }, "require": { - "paragonie/constant_time_encoding": "^1|^2", + "paragonie/constant_time_encoding": "^1|^2|^3", "paragonie/random_compat": "^1.4|^2.0|^9.99.99", "php": ">=5.6.1" }, @@ -3893,7 +3893,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.37" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.39" }, "funding": [ { @@ -3909,7 +3909,7 @@ "type": "tidelift" } ], - "time": "2024-03-03T02:14:58+00:00" + "time": "2024-06-24T06:27:33+00:00" }, { "name": "promphp/prometheus_client_php", @@ -4210,16 +4210,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", "shasum": "" }, "require": { @@ -4269,7 +4269,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0" }, "funding": [ { @@ -4285,20 +4285,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-05-31T15:07:36+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "version": "v1.30.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", "shasum": "" }, "require": { @@ -4349,7 +4349,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0" }, "funding": [ { @@ -4365,7 +4365,7 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-06-19T12:30:46+00:00" }, { "name": "vlucas/phpdotenv", @@ -4508,7 +4508,2376 @@ "time": "2022-06-03T18:03:27+00:00" } ], - "packages-dev": [], + "packages-dev": [ + { + "name": "amphp/php-cs-fixer-config", + "version": "v2.1.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/php-cs-fixer-config.git", + "reference": "0fad9ec6a10a0a58fbf8cb77f41da34f80c031d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/php-cs-fixer-config/zipball/0fad9ec6a10a0a58fbf8cb77f41da34f80c031d6", + "reference": "0fad9ec6a10a0a58fbf8cb77f41da34f80c031d6", + "shasum": "" + }, + "require": { + "friendsofphp/php-cs-fixer": "^3.5", + "php": ">=7.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\CodeStyle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Sascha-Oliver Prolic", + "email": "saschaprolic@googlemail.com" + } + ], + "description": "Code style config for AMPHP.", + "support": { + "issues": "https://github.com/amphp/php-cs-fixer-config/issues", + "source": "https://github.com/amphp/php-cs-fixer-config/tree/v2.1.0" + }, + "funding": [ + { + "url": "https://github.com/amphp", + "type": "github" + } + ], + "time": "2024-04-19T02:16:09+00:00" + }, + { + "name": "clue/ndjson-react", + "version": "v1.3.0", + "source": { + "type": "git", + "url": "https://github.com/clue/reactphp-ndjson.git", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", + "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", + "shasum": "" + }, + "require": { + "php": ">=5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "support": { + "issues": "https://github.com/clue/reactphp-ndjson/issues", + "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.1.4", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "04229f163664973f68f38f6f73d917799168ef24" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", + "reference": "04229f163664973f68f38f6f73d917799168ef24", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-27T13:40:54+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.2" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-07-12T11:35:52+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.5" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "support": { + "issues": "https://github.com/igorw/evenement/issues", + "source": "https://github.com/igorw/evenement/tree/v3.0.2" + }, + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "support": { + "issues": "https://github.com/theofidry/cpu-core-counter/issues", + "source": "https://github.com/theofidry/cpu-core-counter/tree/1.1.0" + }, + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.59.3", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/30ba9ecc2b0e5205e578fe29973c15653d9bfd29", + "reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.0", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.3", + "infection/infection": "^0.29.5", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", + "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.59.3" + }, + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2024-06-16T14:17:03+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "support": { + "issues": "https://github.com/php-fig/event-dispatcher/issues", + "source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0" + }, + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/cache/issues", + "source": "https://github.com/reactphp/cache/tree/v1.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.5", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "react/socket": "^1.8", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/child-process/issues", + "source": "https://github.com/reactphp/child-process/tree/v0.6.5" + }, + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-09-16T13:41:56+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "support": { + "issues": "https://github.com/reactphp/dns/issues", + "source": "https://github.com/reactphp/dns/tree/v1.13.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "support": { + "issues": "https://github.com/reactphp/event-loop/issues", + "source": "https://github.com/reactphp/event-loop/tree/v1.5.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.2.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.11", + "react/event-loop": "^1.2", + "react/promise": "^3 || ^2.6 || ^1.2.1", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4 || ^3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.10" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "support": { + "issues": "https://github.com/reactphp/socket/issues", + "source": "https://github.com/reactphp/socket/tree/v1.15.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-12-15T11:02:10+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "support": { + "issues": "https://github.com/reactphp/stream/issues", + "source": "https://github.com/reactphp/stream/tree/v1.4.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "6.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "6.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-07-03T04:53:05+00:00" + }, + { + "name": "symfony/console", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "0aa29ca177f432ab68533432db0de059f39c92ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", + "reference": "0aa29ca177f432ab68533432db0de059f39c92ae", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^6.4|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T10:03:55+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/92a91985250c251de9b947a14bb2c9390b1a562c", + "reference": "92a91985250c251de9b947a14bb2c9390b1a562c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T10:03:55+00:00" + }, + { + "name": "symfony/finder", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "require-dev": { + "symfony/filesystem": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/process", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "febf90124323a093c7ee06fdb30e765ca3c20028" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", + "reference": "febf90124323a093c7ee06fdb30e765ca3c20028", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v7.1.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:57:53+00:00" + }, + { + "name": "symfony/string", + "version": "v7.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", + "reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "support": { + "source": "https://github.com/symfony/string/tree/v7.1.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T09:27:18+00:00" + } + ], "aliases": [], "minimum-stability": "dev", "stability-flags": { @@ -4518,7 +6887,7 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.1", + "php": "^8.2", "ext-json": "*", "ext-mbstring": "*" }, diff --git a/config.php b/config.php index 19e886c..5af26f3 100644 --- a/config.php +++ b/config.php @@ -3,6 +3,8 @@ use danog\MadelineProto\Logger; use TelegramApiServer\EventObservers\LogObserver; +use function Amp\Socket\SocketAddress\fromString; + $settings = [ 'server' => [ 'address' => (string)getenv('SERVER_ADDRESS'), @@ -58,6 +60,10 @@ $settings = [ 'report_broken_media' => false, 'download_parallel_chunks' => 20, ], + 'metrics' => [ + 'enable_prometheus_collection' => true, //(bool)getenv("PROMETHEUS_BIND_TO"), + 'metrics_bind_to' => fromString("0.0.0.0:12345") + ] ], 'api' => [ 'ip_whitelist' => array_filter( @@ -79,4 +85,4 @@ if (empty($settings['telegram']['app_info']['api_id'])) { throw new InvalidArgumentException('Need to fill TELEGRAM_API_ID in .env.docker or .env'); } -return $settings; \ No newline at end of file +return $settings; diff --git a/docker/php/conf.d/xdebug.ini b/dev/xdebug.ini similarity index 62% rename from docker/php/conf.d/xdebug.ini rename to dev/xdebug.ini index 4d11575..2ed9594 100644 --- a/docker/php/conf.d/xdebug.ini +++ b/dev/xdebug.ini @@ -1,5 +1,8 @@ +opcache.jit=disable +zend_extension=xdebug.so + xdebug.max_nesting_level=512 xdebug.mode=debug xdebug.start_with_request=yes xdebug.client_host=host.docker.internal -xdebug.client_port=9003 \ No newline at end of file +xdebug.client_port=9003 diff --git a/docker-compose.base.yml b/docker-compose.base.yml index 475d15a..6de8e6d 100644 --- a/docker-compose.base.yml +++ b/docker-compose.base.yml @@ -19,7 +19,7 @@ services: max-size: "1024k" max-file: "2" healthcheck: - test: timeout 15 curl -f http://localhost:9503/system/healthcheck || bash -c 'kill -INT -1 && (sleep 5; kill -s 9 -1)' + test: timeout 15 curl -f http://localhost:9503/system/healthcheck || sh -c 'kill -INT -1 && (sleep 5; kill -s 9 -1)' interval: 60s timeout: 30s retries: 1 diff --git a/docker-compose.yml b/docker-compose.yml index 3352a94..74eb406 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: service: base-api ports: - "127.0.0.1:9503:9503" + - "12345:12345" command: - "-s=session" mysql: @@ -15,4 +16,4 @@ services: - "127.0.0.1:9507:3306" networks: default: - name: telegram-api-server \ No newline at end of file + name: telegram-api-server diff --git a/docker/php/conf.d/common.ini b/docker/php/conf.d/common.ini deleted file mode 100644 index 7fc1f83..0000000 --- a/docker/php/conf.d/common.ini +++ /dev/null @@ -1 +0,0 @@ -zend.assertions=-1 \ No newline at end of file diff --git a/docker/php/conf.d/opcache.ini b/docker/php/conf.d/opcache.ini deleted file mode 100644 index 14ef174..0000000 --- a/docker/php/conf.d/opcache.ini +++ /dev/null @@ -1,11 +0,0 @@ -; Extended PHP.ini file to enable JIT. -; ==================================== -; Place this file under /usr/local/etc/php/conf.d/ -zend_extension=opcache.so -opcache.enable=1 -opcache.enable_cli=1 -opcache.validate_timestamps=1 -opcache.revalidate_freq=0 -opcache.huge_code_pages=1 -opcache.jit_buffer_size=100M -opcache.jit=function \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh index 60fc295..e985466 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,4 +1,14 @@ -#!/usr/bin/env bash +#!/usr/bin/env sh + +VERSION=1.0.0 +CURRENT_VERSION=$(cat /tas_version) + +if [ "$VERSION" != "$CURRENT_VERSION" ]; then + echo "Wrong docker image version, expected $VERSION, got $CURRENT_VERSION, please run docker compose pull!" + exit 1 +fi + +composer install docker-compose-wait \ -&& nice -n 20 php server.php -e=.env.docker --docker "$@" \ No newline at end of file +&& nice -n 20 php server.php -e=.env.docker --docker "$@" diff --git a/examples/websocket-events.php b/examples/websocket-events.php index 9682bad..2b54b13 100644 --- a/examples/websocket-events.php +++ b/examples/websocket-events.php @@ -1,7 +1,7 @@ -startNotLoggedInSessions(); - $sessionsCount = count($sessionFiles); + $sessionsCount = \count($sessionFiles); warning( "\nTelegramApiServer ready." . "\nNumber of sessions: {$sessionsCount}." ); } + private static LocalKeyedMutex $mutex; + public function addSession(string $session, array $settings = []): API { + self::$mutex ??= new LocalKeyedMutex; + $lock = self::$mutex->acquire($session); if (isset($this->instances[$session])) { throw new InvalidArgumentException('Session already exists'); } @@ -58,8 +62,8 @@ class Client if ($settings) { Files::saveSessionSettings($session, $settings); } - $settings = array_replace_recursive( - (array)Config::getInstance()->get('telegram'), + $settings = \array_replace_recursive( + (array) Config::getInstance()->get('telegram'), Files::getSessionSettings($session), ); @@ -87,14 +91,9 @@ class Client $instance->unsetEventHandler(); } unset($instance); - gc_collect_cycles(); + \gc_collect_cycles(); } - /** - * @param string|null $session - * - * @return API - */ public function getSession(?string $session = null): API { if (!$this->instances) { @@ -104,8 +103,8 @@ class Client } if (!$session) { - if (count($this->instances) === 1) { - $session = (string)array_key_first($this->instances); + if (\count($this->instances) === 1) { + $session = (string) \array_key_first($this->instances); } else { throw new InvalidArgumentException( 'Multiple sessions detected. Specify which session to use. See README for examples.' @@ -160,9 +159,10 @@ class Client return $wrapper; } - private static function getSettingsFromArray(string $session, array $settings, SettingsAbstract $settingsObject = new Settings()): SettingsAbstract { + private static function getSettingsFromArray(string $session, array $settings, SettingsAbstract $settingsObject = new Settings()): SettingsAbstract + { foreach ($settings as $key => $value) { - if (is_array($value) && $key !== 'proxies') { + if (\is_array($value) && $key !== 'proxies') { if ($key === 'db' && isset($value['type'])) { $type = match ($value['type']) { 'memory' => new Settings\Database\Memory(), @@ -180,23 +180,22 @@ class Client } unset($value[$value['type']], $value['type'],); - if (count($value) === 0) { + if (\count($value) === 0) { continue; } } - $method = 'get' . ucfirst(str_replace('_', '', ucwords($key, '_'))); + $method = 'get' . \ucfirst(\str_replace('_', '', \ucwords($key, '_'))); self::getSettingsFromArray($session, $value, $settingsObject->$method()); } else { - if ($key === 'serializer' && is_string($value)) { + if ($key === 'serializer' && \is_string($value)) { $value = SerializerType::from($value); } - $method = 'set' . ucfirst(str_replace('_', '', ucwords($key, '_'))); + $method = 'set' . \ucfirst(\str_replace('_', '', \ucwords($key, '_'))); $settingsObject->$method($value); } } return $settingsObject; } - } diff --git a/src/Config.php b/src/Config.php index 27d2ad7..36b53b3 100644 --- a/src/Config.php +++ b/src/Config.php @@ -1,26 +1,24 @@ -config; foreach ($path as $pathKey) { - if (!is_array($value) || !array_key_exists($pathKey, $value)) { + if (!\is_array($value) || !\array_key_exists($pathKey, $value)) { return null; } $value = &$value[$pathKey]; @@ -53,4 +51,4 @@ class Config return $value; } -} \ No newline at end of file +} diff --git a/src/Controllers/AbstractApiController.php b/src/Controllers/AbstractApiController.php index b2eda2a..e7a40f2 100644 --- a/src/Controllers/AbstractApiController.php +++ b/src/Controllers/AbstractApiController.php @@ -1,4 +1,4 @@ - self::JSON_HEADER, 'success' => false, @@ -83,19 +80,19 @@ abstract class AbstractApiController } /** - * Получаем параметры из GET и POST + * Получаем параметры из GET и POST. * */ private function resolveRequest(): void { $query = $this->request->getUri()->getQuery(); - $contentType = (string)$this->request->getHeader('Content-Type'); + $contentType = (string) $this->request->getHeader('Content-Type'); - parse_str($query, $get); + \parse_str($query, $get); switch (true) { case $contentType === 'application/x-www-form-urlencoded': - case mb_strpos($contentType, 'multipart/form-data') !== false: + case \str_contains($contentType, 'multipart/form-data'): $form = (new StreamingFormParser())->parseForm($this->request); $post = []; @@ -107,27 +104,27 @@ abstract class AbstractApiController //We need to break loop without getting file //All other post field will be omitted, hope we dont need them :) break; - } else { - $post[$field->getName()] = $field->buffer(); } + $post[$field->getName()] = $field->buffer(); + } break; case $contentType === 'application/json': $body = $this->request->getBody()->buffer(); - $post = json_decode($body, 1); + $post = \json_decode($body, true); break; default: $body = $this->request->getBody()->buffer(); - parse_str($body, $post); + \parse_str($body, $post); } - $this->parameters = array_merge((array)$post, $get); - $this->parameters = array_values($this->parameters); + $this->parameters = \array_merge((array) $post, $get); + $this->parameters = \array_values($this->parameters); } /** - * Получает посты для формирования ответа + * Получает посты для формирования ответа. * */ private function generateResponse(): void @@ -159,16 +156,16 @@ abstract class AbstractApiController protected function callApiCommon(API $madelineProto) { - $pathCount = count($this->api); - if ($pathCount === 1 && method_exists($this->extensionClass, $this->api[0])) { + $pathCount = \count($this->api); + if ($pathCount === 1 && \method_exists($this->extensionClass, $this->api[0])) { /** @var ApiExtensions|SystemApiExtensions $madelineProtoExtensions */ $madelineProtoExtensions = new $this->extensionClass($madelineProto, $this->request, $this->file); $result = $madelineProtoExtensions->{$this->api[0]}(...$this->parameters); } else { if ($this->api[0] === 'API') { $madelineProto = Client::getWrapper($madelineProto)->getAPI(); - array_shift($this->api); - $pathCount = count($this->api); + \array_shift($this->api); + $pathCount = \count($this->api); } //Проверяем нет ли в MadilineProto такого метода. switch ($pathCount) { @@ -190,9 +187,7 @@ abstract class AbstractApiController } /** - * @param Throwable $e * - * @return AbstractApiController * @throws Throwable */ private function setError(Throwable $e): self @@ -210,9 +205,8 @@ abstract class AbstractApiController } /** - * Кодирует ответ в нужный формат: json + * Кодирует ответ в нужный формат: json. * - * @return Response|string * @throws JsonException */ private function getResponse(): string|Response @@ -230,7 +224,7 @@ abstract class AbstractApiController $data['success'] = true; } - $result = json_encode( + $result = \json_encode( $data, JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE | @@ -244,15 +238,13 @@ abstract class AbstractApiController } /** - * Устанавливает http код ответа (200, 400, 404 и тд.) + * Устанавливает http код ответа (200, 400, 404 и тд.). * - * @param int $code * - * @return AbstractApiController */ private function setPageCode(int $code): self { $this->page['code'] = $this->page['code'] === 200 ? $code : $this->page['code']; return $this; } -} \ No newline at end of file +} diff --git a/src/Controllers/ApiController.php b/src/Controllers/ApiController.php index 8bd7969..39f75a9 100644 --- a/src/Controllers/ApiController.php +++ b/src/Controllers/ApiController.php @@ -1,35 +1,25 @@ -session = $path['session'] ?? null; - $this->api = explode('.', $path['method'] ?? ''); + $this->api = \explode('.', $path['method'] ?? ''); } /** @@ -44,37 +34,6 @@ class ApiController extends AbstractApiController return $this->callApiCommon($madelineProto); } - //GROUP REQUESTS IN BULKS - static $futures = []; - - $futures[] = $future = async($this->callApiCommon(...), $madelineProto); - delay($this->waitNextTick()); - - if ($futures) { - awaitAll($futures); - Logger::getInstance()->notice("Executed bulk requests:" . count($futures)); - $futures = []; - } - - return $future->await(); + return $this->callApiCommon($madelineProto); } - - /** - * Sync threads execution via time ticks - * Need to enable madelineProto futures bulk execution - * @param float $tick interval of execution in seconds. - */ - protected function waitNextTick(float $tick = 0.5): float { - $tickMs = $tick * 1000; - $now = (int)(microtime(true) * 1000); - $currentTick = intdiv((int)(microtime(true) * 1000), $tickMs); - $nextTick = ($currentTick + 1); - $nextTickTime = $nextTick * $tickMs; - $wait = round(($nextTickTime - $now)/1000, 3); - - Logger::getInstance()->notice("Waiting $wait seconds before tick"); - - return $wait; - } - -} \ No newline at end of file +} diff --git a/src/Controllers/EventsController.php b/src/Controllers/EventsController.php index 9d35878..f8110cf 100644 --- a/src/Controllers/EventsController.php +++ b/src/Controllers/EventsController.php @@ -1,11 +1,11 @@ - $client->ping()); + $pingLoop = EventLoop::repeat(self::PING_INTERVAL_MS, static fn () => $client->ping()); $client->onClose(static function () use ($clientId, $requestedSession, $pingLoop) { EventLoop::cancel($pingLoop); @@ -104,7 +104,7 @@ class EventsController implements WebsocketClientHandler, WebsocketAcceptor ]; $this->gateway->multicastText( - json_encode( + \json_encode( $update, JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE | @@ -115,4 +115,4 @@ class EventsController implements WebsocketClientHandler, WebsocketAcceptor ); }); } -} \ No newline at end of file +} diff --git a/src/Controllers/LogController.php b/src/Controllers/LogController.php index 0f4bb14..f856ef9 100644 --- a/src/Controllers/LogController.php +++ b/src/Controllers/LogController.php @@ -1,4 +1,4 @@ -getId(); - $pingLoop = EventLoop::repeat(self::PING_INTERVAL_MS, static fn() => $client->ping()); + $pingLoop = EventLoop::repeat(self::PING_INTERVAL_MS, static fn () => $client->ping()); $client->onClose(static function () use ($clientId, $pingLoop) { EventLoop::cancel($pingLoop); @@ -90,7 +90,7 @@ class LogController implements WebsocketClientHandler, WebsocketAcceptor ]; $this->gateway->multicastText( - json_encode( + \json_encode( $update, JSON_THROW_ON_ERROR | JSON_INVALID_UTF8_SUBSTITUTE | @@ -101,4 +101,4 @@ class LogController implements WebsocketClientHandler, WebsocketAcceptor ); }); } -} \ No newline at end of file +} diff --git a/src/Controllers/SystemController.php b/src/Controllers/SystemController.php index 1159b4c..23bc3e6 100644 --- a/src/Controllers/SystemController.php +++ b/src/Controllers/SystemController.php @@ -1,25 +1,23 @@ -api = explode('.', $path['method'] ?? ''); + $this->api = \explode('.', $path['method'] ?? ''); } /** - * @return mixed * @throws Exception */ protected function callApi() @@ -29,4 +27,4 @@ class SystemController extends AbstractApiController return $result; } -} \ No newline at end of file +} diff --git a/src/EventObservers/EventHandler.php b/src/EventObservers/EventHandler.php index 7a045d9..4442de9 100644 --- a/src/EventObservers/EventHandler.php +++ b/src/EventObservers/EventHandler.php @@ -4,7 +4,7 @@ namespace TelegramApiServer\EventObservers; use TelegramApiServer\Files; -class EventHandler extends \danog\MadelineProto\EventHandler +final class EventHandler extends \danog\MadelineProto\EventHandler { public static array $instances = []; private string $sessionName; @@ -12,8 +12,8 @@ class EventHandler extends \danog\MadelineProto\EventHandler public function onStart() { $this->sessionName = Files::getSessionName($this->wrapper->getSession()->getSessionPath()); - if (empty(static::$instances[$this->sessionName])) { - static::$instances[$this->sessionName] = true; + if (empty(self::$instances[$this->sessionName])) { + self::$instances[$this->sessionName] = true; warning("Event observer CONSTRUCTED: {$this->sessionName}"); } } @@ -23,7 +23,7 @@ class EventHandler extends \danog\MadelineProto\EventHandler if (empty($this->sessionName)) { return; } - unset(static::$instances[$this->sessionName]); + unset(self::$instances[$this->sessionName]); warning("Event observer DESTRUCTED: {$this->sessionName}"); } @@ -32,4 +32,4 @@ class EventHandler extends \danog\MadelineProto\EventHandler info("Received update from session: {$this->sessionName}"); EventObserver::notify($update, $this->sessionName); } -} \ No newline at end of file +} diff --git a/src/EventObservers/EventObserver.php b/src/EventObservers/EventObserver.php index 47b42bc..aa242d9 100644 --- a/src/EventObservers/EventObserver.php +++ b/src/EventObservers/EventObserver.php @@ -1,15 +1,14 @@ - $callback) { + foreach (self::$subscribers as $clientId => $callback) { notice("Pass update to callback. ClientId: {$clientId}"); $callback($update, $sessionName); } @@ -26,16 +25,16 @@ class EventObserver private static function addSessionClient(string $session): void { - if (empty(static::$sessionClients[$session])) { - static::$sessionClients[$session] = 0; + if (empty(self::$sessionClients[$session])) { + self::$sessionClients[$session] = 0; } - ++static::$sessionClients[$session]; + ++self::$sessionClients[$session]; } private static function removeSessionClient(string $session): void { - if (!empty(static::$sessionClients[$session])) { - --static::$sessionClients[$session]; + if (!empty(self::$sessionClients[$session])) { + --self::$sessionClients[$session]; } } @@ -43,14 +42,14 @@ class EventObserver { $sessions = []; if ($requestedSession === null) { - $sessions = array_keys(Client::getInstance()->instances); + $sessions = \array_keys(Client::getInstance()->instances); } else { $sessions[] = $requestedSession; } foreach ($sessions as $session) { - static::addSessionClient($session); - if (static::$sessionClients[$session] === 1) { + self::addSessionClient($session); + if (self::$sessionClients[$session] === 1) { warning("Start EventHandler: {$session}"); try { $instance = Client::getInstance()->getSession($session); @@ -60,7 +59,7 @@ class EventObserver EventHandler::cachePlugins(EventHandler::class); $wrapper->getAPI()->setEventHandler(EventHandler::class); } catch (Throwable $e) { - static::removeSessionClient($session); + self::removeSessionClient($session); error('Cant set EventHandler', [ 'session' => $session, 'exception' => Logger::getExceptionAsArray($e), @@ -74,19 +73,19 @@ class EventObserver { $sessions = []; if ($requestedSession === null) { - $sessions = array_keys(Client::getInstance()->instances); + $sessions = \array_keys(Client::getInstance()->instances); } else { $sessions[] = $requestedSession; } foreach ($sessions as $session) { - static::removeSessionClient($session); - if (empty(static::$sessionClients[$session]) || $force) { + self::removeSessionClient($session); + if (empty(self::$sessionClients[$session]) || $force) { warning("Stopping EventHandler: {$session}"); Client::getInstance()->instances[$session]->unsetEventHandler(); - unset(EventHandler::$instances[$session], static::$sessionClients[$session]); + unset(EventHandler::$instances[$session], self::$sessionClients[$session]); } } } -} \ No newline at end of file +} diff --git a/src/EventObservers/LogObserver.php b/src/EventObservers/LogObserver.php index e6b08f3..cdbfa9f 100644 --- a/src/EventObservers/LogObserver.php +++ b/src/EventObservers/LogObserver.php @@ -1,39 +1,39 @@ - $callback) { + foreach (self::$subscribers as $clientId => $callback) { $callback($level, $message, $context); } } /** * @param mixed|array|string $message - * @param int $level */ public static function log($message, int $level) { - if (is_scalar($message)) { - Logger::getInstance()->log(Logger::$madelineLevels[$level], (string)$message); + if (\is_scalar($message)) { + Logger::getInstance()->log(Logger::$madelineLevels[$level], (string) $message); } else { if ($message instanceof Throwable) { $message = Logger::getExceptionAsArray($message); } - if (is_array($message)) { + if (\is_array($message)) { Logger::getInstance()->log(Logger::$madelineLevels[$level], '', $message); } else { Logger::getInstance()->log( Logger::$madelineLevels[$level], - json_encode($message, + \json_encode( + $message, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_INVALID_UTF8_SUBSTITUTE | @@ -43,4 +43,4 @@ class LogObserver } } } -} \ No newline at end of file +} diff --git a/src/EventObservers/ObserverTrait.php b/src/EventObservers/ObserverTrait.php index b02b3c5..463b148 100644 --- a/src/EventObservers/ObserverTrait.php +++ b/src/EventObservers/ObserverTrait.php @@ -1,8 +1,7 @@ - */ -class Logger extends AbstractLogger +final class Logger extends AbstractLogger { private static ?Logger $instanse = null; @@ -54,13 +57,19 @@ class Logger extends AbstractLogger private static string $dateTimeFormat = 'Y-m-d H:i:s'; public int $minLevelIndex; - private array $formatter; + private \Closure $formatter; - protected function __construct(string $minLevel = LogLevel::WARNING, callable $formatter = null) + private WritableStream $stdout; + /** + * @var array + */ + private static array $closePromises = []; + + protected function __construct(string $minLevel = LogLevel::WARNING, ?\Closure $formatter = null) { if (null === $minLevel) { if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) { - switch ((int)(isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] : + switch ((int) (isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] : $_SERVER['SHELL_VERBOSITY'])) { case -1: $minLevel = LogLevel::ERROR; @@ -79,23 +88,34 @@ class Logger extends AbstractLogger } if (!isset(self::$levels[$minLevel])) { - throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $minLevel)); + throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $minLevel)); } - $this->minLevelIndex = self::$levels[$minLevel]; - $this->formatter = $formatter ?: [$this, 'format']; + $this->minLevelIndex = \min(self::$levels[$minLevel], self::$levels[self::$madelineLevels[MadelineProto\Logger::VERBOSE]]); + $this->formatter = $formatter ?: $this->format(...); + $pipe = new Pipe(PHP_INT_MAX); + $this->stdout = $pipe->getSink(); + $source = $pipe->getSource(); + $promise = async(static function () use ($source, &$promise): void { + try { + pipe($source, getStdout()); + } finally { + unset(self::$closePromises[\spl_object_id($promise)]); + } + }); + self::$closePromises[\spl_object_id($promise)] = [$this->stdout, $promise]; } public static function getInstance(): Logger { - if (!static::$instanse) { + if (!self::$instanse) { $settings = Config::getInstance()->get('telegram'); - $loggerLevel = static::$madelineLevels[$settings['logger']['level']]; - static::$instanse = new static($loggerLevel); + $loggerLevel = self::$madelineLevels[$settings['logger']['level']]; + self::$instanse = new static($loggerLevel); } - return static::$instanse; + return self::$instanse; } /** @@ -104,7 +124,7 @@ class Logger extends AbstractLogger public function log($level, $message, array $context = []): void { if (!isset(self::$levels[$level])) { - throw new InvalidArgumentException(sprintf('The log level "%s" does not exist.', $level)); + throw new InvalidArgumentException(\sprintf('The log level "%s" does not exist.', $level)); } LogObserver::notify($level, $message, $context); @@ -114,61 +134,77 @@ class Logger extends AbstractLogger } $formatter = $this->formatter; - /** @see Logger::format */ - echo $formatter($level, $message, $context); + $data = $formatter($level, $message, $context); + ; + try { + $this->stdout->write($data); + } catch (\Throwable) { + echo $data; + } + } + + /** + * @internal Internal function used to flush the log buffer on shutdown. + */ + public static function finalize(): void + { + foreach (self::$closePromises as [$stdout, $promise]) { + $stdout->close(); + $promise->await(); + } } private function format(string $level, string $message, array $context): string { - if (false !== strpos($message, '{')) { + if (false !== \strpos($message, '{')) { $replacements = []; foreach ($context as $key => $val) { if ($val instanceof Throwable) { $context[$key] = self::getExceptionAsArray($val); } - if (null === $val || is_scalar($val) || (is_object($val) && method_exists($val, '__toString'))) { + if (null === $val || \is_scalar($val) || (\is_object($val) && \method_exists($val, '__toString'))) { $replacements["{{$key}}"] = $val; } else { if ($val instanceof DateTimeInterface) { - $replacements["{{$key}}"] = $val->format(static::$dateTimeFormat); + $replacements["{{$key}}"] = $val->format(self::$dateTimeFormat); } else { - if (is_object($val)) { - $replacements["{{$key}}"] = '[object ' . get_class($val) . ']'; + if (\is_object($val)) { + $replacements["{{$key}}"] = '[object ' . \get_class($val) . ']'; } else { - $replacements["{{$key}}"] = '[' . gettype($val) . ']'; + $replacements["{{$key}}"] = '[' . \gettype($val) . ']'; } } } } - $message = strtr($message, $replacements); + $message = \strtr($message, $replacements); } - return sprintf( - '[%s] [%s] %s %s', - date(static::$dateTimeFormat), - $level, - $message, - $context ? + return \sprintf( + '[%s] [%s] %s %s', + \date(self::$dateTimeFormat), + $level, + $message, + $context ? "\n" . - json_encode( + \json_encode( $context, JSON_UNESCAPED_UNICODE | JSON_INVALID_UTF8_SUBSTITUTE | JSON_PRETTY_PRINT | JSON_UNESCAPED_LINE_TERMINATORS | JSON_UNESCAPED_SLASHES ) : '' - ) . PHP_EOL; + ) . PHP_EOL; } public static function getExceptionAsArray(Throwable $exception) { return [ - 'exception' => get_class($exception), + 'exception' => \get_class($exception), 'message' => $exception->getMessage(), 'file' => $exception->getFile(), 'line' => $exception->getLine(), 'code' => $exception->getCode(), - 'backtrace' => array_slice($exception->getTrace(), 0, 3), + 'backtrace' => \array_slice($exception->getTrace(), 0, 3), 'previous exception' => $exception->getPrevious(), ]; } -} \ No newline at end of file +} diff --git a/src/MadelineProtoExtensions/ApiExtensions.php b/src/MadelineProtoExtensions/ApiExtensions.php index c0c8b73..19480c6 100644 --- a/src/MadelineProtoExtensions/ApiExtensions.php +++ b/src/MadelineProtoExtensions/ApiExtensions.php @@ -1,9 +1,7 @@ - &$nextEntity) { @@ -105,7 +100,7 @@ class ApiExtensions } if ($nextEntity['offset'] < ($entity['offset'] + $entity['length'])) { $nextEntity['offset'] += StrTools::mbStrlen( - preg_replace('~(\>).*<\/.*$~', '$1', $textFormated) + \preg_replace('~(\>).*<\/.*$~', '$1', $textFormated) ); } else { $nextEntity['offset'] += StrTools::mbStrlen($textFormated) - StrTools::mbStrlen($text); @@ -115,7 +110,7 @@ class ApiExtensions } } unset($entity); - $message = nl2br($message); + $message = \nl2br($message); return $message; } @@ -127,7 +122,7 @@ class ApiExtensions } /** - * Пересылает сообщения без ссылки на оригинал + * Пересылает сообщения без ссылки на оригинал. * * @param array $data *
@@ -141,7 +136,7 @@ class ApiExtensions
      */
     public function copyMessages(array $data)
     {
-        $data = array_merge(
+        $data = \array_merge(
             [
                 'from_peer' => '',
                 'to_peer' => '',
@@ -157,7 +152,7 @@ class ApiExtensions
             ]
         );
         $result = [];
-        if (!$response || !is_array($response) || !array_key_exists('messages', $response)) {
+        if (!$response || !\is_array($response) || !\array_key_exists('messages', $response)) {
             return $result;
         }
 
@@ -167,14 +162,14 @@ class ApiExtensions
                 'peer' => $data['to_peer'],
                 'entities' => $message['entities'] ?? [],
             ];
-            if (static::hasMedia($message, false)) {
+            if (self::hasMedia($message, false)) {
                 $messageData['media'] = $message; //MadelineProto сама достанет все media из сообщения.
                 $result[] = $this->madelineProto->messages->sendMedia(...$messageData);
             } else {
                 $result[] = $this->madelineProto->messages->sendMessage(...$messageData);
             }
             if ($key > 0) {
-                delay(random_int(300, 2000) / 1000);
+                delay(\random_int(300, 2000) / 1000);
             }
         }
 
@@ -182,14 +177,13 @@ class ApiExtensions
     }
 
     /**
-     * Загружает медиафайл из указанного сообщения в поток
+     * Загружает медиафайл из указанного сообщения в поток.
      *
-     * @param array $data
      *
      */
     public function getMedia(array $data): Response
     {
-        $data = array_merge(
+        $data = \array_merge(
             [
                 'peer' => '',
                 'id' => [0],
@@ -203,7 +197,7 @@ class ApiExtensions
             throw new NoMediaException('Empty message');
         }
 
-        if (!static::hasMedia($message, true)) {
+        if (!self::hasMedia($message, true)) {
             throw new NoMediaException('Message has no media');
         }
 
@@ -222,17 +216,16 @@ class ApiExtensions
             }
         }
 
-
         return $this->downloadToResponse($info);
     }
 
     /**
-     * Загружает превью медиафайла из указанного сообщения в поток
+     * Загружает превью медиафайла из указанного сообщения в поток.
      *
      */
     public function getMediaPreview(array $data): Response
     {
-        $data = array_merge(
+        $data = \array_merge(
             [
                 'peer' => '',
                 'id' => [0],
@@ -246,11 +239,11 @@ class ApiExtensions
             throw new NoMediaException('Empty message');
         }
 
-        if (!static::hasMedia($message, true)) {
+        if (!self::hasMedia($message, true)) {
             throw new NoMediaException('Message has no media');
         }
 
-        $media = $message['media'][array_key_last($message['media'])];
+        $media = $message['media'][\array_key_last($message['media'])];
         $thumb = null;
         switch (true) {
             case isset($media['sizes']):
@@ -308,15 +301,15 @@ class ApiExtensions
     public function getMessages(array $data): array
     {
         $peerInfo = $this->madelineProto->getInfo($data['peer']);
-        if (in_array($peerInfo['type'], ['channel', 'supergroup'])) {
+        if (\in_array($peerInfo['type'], ['channel', 'supergroup'])) {
             $response = $this->madelineProto->channels->getMessages(
                 [
                     'channel' => $data['peer'],
-                    'id' => (array)$data['id'],
+                    'id' => (array) $data['id'],
                 ]
             );
         } else {
-            $response = $this->madelineProto->messages->getMessages(['id' => (array)$data['id']]);
+            $response = $this->madelineProto->messages->getMessages(['id' => (array) $data['id']]);
         }
 
         return $response;
@@ -328,7 +321,6 @@ class ApiExtensions
      * @param array $info
      *      Any downloadable array: message, media etc...
      *
-     * @return Response
      */
     public function downloadToResponse(array $info): Response
     {
@@ -336,7 +328,7 @@ class ApiExtensions
     }
 
     /**
-     * Адаптер для стандартного метода
+     * Адаптер для стандартного метода.
      *
      */
     public function downloadToBrowser(array $info): Response
@@ -361,7 +353,7 @@ class ApiExtensions
             $this->file->getMimeType(),
             $this->file->getFilename()
         );
-        $inputFile['id'] = unpack('P', $inputFile['id'])['1'];
+        $inputFile['id'] = \unpack('P', $inputFile['id'])['1'];
         return [
             'media' => [
                 '_' => 'inputMediaUploadedDocument',
@@ -384,7 +376,6 @@ class ApiExtensions
         Client::getWrapper($this->madelineProto)->serialize();
     }
 
-
     public function getUpdates(array $params): array
     {
         foreach ($params as $key => $value) {
@@ -410,12 +401,13 @@ class ApiExtensions
         Client::getWrapper($this->madelineProto)->serialize();
     }
 
-    public function unsubscribeFromUpdates(?string $channel = null): array {
+    public function unsubscribeFromUpdates(?string $channel = null): array
+    {
         $inputChannelId = null;
         if ($channel) {
             $id = (string) $this->madelineProto->getId($channel);
 
-            $inputChannelId = (int)str_replace(['-100', '-'], '', $id);
+            $inputChannelId = (int) \str_replace(['-100', '-'], '', $id);
             if (!$inputChannelId) {
                 throw new InvalidArgumentException('Invalid id');
             }
@@ -438,11 +430,10 @@ class ApiExtensions
             $counter++;
         }
 
-
         return [
             'disabled_update_loops' => $counter,
-            'current_update_loops' => count(Client::getWrapper($this->madelineProto)->getAPI()->feeders),
+            'current_update_loops' => \count(Client::getWrapper($this->madelineProto)->getAPI()->feeders),
         ];
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/MadelineProtoExtensions/SystemApiExtensions.php b/src/MadelineProtoExtensions/SystemApiExtensions.php
index a78a223..73c0aa4 100644
--- a/src/MadelineProtoExtensions/SystemApiExtensions.php
+++ b/src/MadelineProtoExtensions/SystemApiExtensions.php
@@ -1,4 +1,4 @@
-client->addSession($session, $settings);
@@ -75,7 +75,7 @@ class SystemApiExtensions
         foreach ($this->client->instances as $session => $instance) {
             $authorized = $instance->getAuthorization();
             switch ($authorized) {
-                case API::NOT_LOGGED_IN;
+                case API::NOT_LOGGED_IN:
                     $status = 'NOT_LOGGED_IN';
                     break;
                 case API::WAITING_CODE:
@@ -107,7 +107,7 @@ class SystemApiExtensions
 
         return [
             'sessions' => $sessions,
-            'memory' => $this->bytesToHuman(memory_get_usage(true)),
+            'memory' => $this->bytesToHuman(\memory_get_usage(true)),
         ];
     }
 
@@ -115,10 +115,10 @@ class SystemApiExtensions
     {
         $file = Files::getSessionFile($session);
 
-        if (is_file($file)) {
+        if (\is_file($file)) {
             $futures = [];
-            foreach (glob("$file*") as $file) {
-                $futures[] = async(fn() => deleteFile($file));
+            foreach (\glob("$file*") as $file) {
+                $futures[] = async(fn () => deleteFile($file));
             }
             awaitAll($futures);
         } else {
@@ -140,7 +140,7 @@ class SystemApiExtensions
     public function unlinkSessionSettings($session): string
     {
         $settings = Files::getSessionFile($session, Files::SETTINGS_EXTENSION);
-        if (is_file($settings)) {
+        if (\is_file($settings)) {
             deleteFile($settings);
         }
 
@@ -149,7 +149,7 @@ class SystemApiExtensions
 
     public function exit(): string
     {
-        EventLoop::defer(static fn() => exit());
+        EventLoop::defer(static fn () => exit());
         return 'ok';
     }
 
@@ -159,6 +159,6 @@ class SystemApiExtensions
         for ($i = 0; $bytes > 1024; $i++) {
             $bytes /= 1024;
         }
-        return round($bytes, 2) . ' ' . $units[$i];
+        return \round($bytes, 2) . ' ' . $units[$i];
     }
-}
\ No newline at end of file
+}
diff --git a/src/Migrations/StartUpFixes.php b/src/Migrations/StartUpFixes.php
index 8b93fa8..6ac2b14 100644
--- a/src/Migrations/StartUpFixes.php
+++ b/src/Migrations/StartUpFixes.php
@@ -1,20 +1,14 @@
-getMethod();
-        $uri = (string)$request->getUri();
+        $uri = (string) $request->getUri();
         $protocolVersion = $request->getProtocolVersion();
         $remote = Server::getClientIp($request);
 
@@ -58,7 +58,7 @@ class AccessLoggerMiddleware implements Middleware
             ],
         ];
 
-        $level = $status < 400 ? LogLevel::INFO : LogLevel::NOTICE;
+        $level = $status < 400 ? LogLevel::DEBUG : LogLevel::INFO;
 
         $this->logger->log(
             $level,
diff --git a/src/Server/Authorization.php b/src/Server/Authorization.php
index b4db07e..47258ad 100644
--- a/src/Server/Authorization.php
+++ b/src/Server/Authorization.php
@@ -1,4 +1,4 @@
-selfIp = ip2long(getHostByName(php_uname('n')));
-        $this->ipWhitelist = (array)Config::getInstance()->get('api.ip_whitelist', []);
+        $this->selfIp = \ip2long(\getHostByName(\php_uname('n')));
+        $this->ipWhitelist = (array) Config::getInstance()->get('api.ip_whitelist', []);
         $this->passwords = Config::getInstance()->get('api.passwords', []);
         if (!$this->ipWhitelist && !$this->passwords) {
             error('API is unprotected! Please specify IP_WHITELIST or PASSWORD in .env.docker');
@@ -37,11 +37,11 @@ class Authorization implements Middleware
         }
 
         if ($this->passwords) {
-            $header = (string)$request->getHeader('Authorization');
+            $header = (string) $request->getHeader('Authorization');
             if ($header) {
-                sscanf($header, "Basic %s", $encodedPassword);
-                [$username, $password] = explode(':', base64_decode($encodedPassword), 2);
-                if (array_key_exists($username, $this->passwords) && $this->passwords[$username] === $password) {
+                \sscanf($header, "Basic %s", $encodedPassword);
+                [$username, $password] = \explode(':', \base64_decode($encodedPassword), 2);
+                if (\array_key_exists($username, $this->passwords) && $this->passwords[$username] === $password) {
                     return $requestHandler->handleRequest($request);
                 }
             }
@@ -59,25 +59,25 @@ class Authorization implements Middleware
     private function isIpAllowed(string $host): bool
     {
 
-
-        if ($this->ipWhitelist && !in_array($host, $this->ipWhitelist, true)) {
+        if ($this->ipWhitelist && !\in_array($host, $this->ipWhitelist, true)) {
             return false;
         }
         return true;
     }
 
-    private function isLocal(string $host): bool {
+    private function isLocal(string $host): bool
+    {
         if ($host === '127.0.0.1' || $host === 'localhost') {
             return true;
         }
 
         global $options;
         if ($options['docker']) {
-            $isSameNetwork = abs(ip2long($host) - $this->selfIp) < 256;
+            $isSameNetwork = \abs(\ip2long($host) - $this->selfIp) < 256;
             if ($isSameNetwork) {
                 return true;
             }
         }
         return false;
     }
-}
\ No newline at end of file
+}
diff --git a/src/Server/ErrorResponses.php b/src/Server/ErrorResponses.php
index ba5307d..736ef81 100644
--- a/src/Server/ErrorResponses.php
+++ b/src/Server/ErrorResponses.php
@@ -1,24 +1,22 @@
- false,
                     'errors' => [
@@ -33,4 +31,4 @@ class ErrorResponses
         );
     }
 
-}
\ No newline at end of file
+}
diff --git a/src/Server/Router.php b/src/Server/Router.php
index 7e96fb1..f3faa43 100644
--- a/src/Server/Router.php
+++ b/src/Server/Router.php
@@ -1,12 +1,12 @@
-router->addRoute('GET', '/log/{level:.*?[^/]}[/]', $logHandler);
     }
 
-
-}
\ No newline at end of file
+}
diff --git a/src/Server/Server.php b/src/Server/Server.php
index 718c9fd..4d8a0be 100644
--- a/src/Server/Server.php
+++ b/src/Server/Server.php
@@ -1,4 +1,4 @@
-stop();
         } else {
             EventLoop::run();
             info("Stopping http server");
             $server->stop();
         }
+        Logger::finalize();
     }
 
     /**
-     * Установить конфигурацию для http-сервера
+     * Установить конфигурацию для http-сервера.
      *
-     * @param array $config
-     * @return array
      */
     private function getConfig(array $config = []): array
     {
-        $config = array_filter($config);
+        $config = \array_filter($config);
 
-        $config = array_merge(
+        $config = \array_merge(
             Config::getInstance()->get('server', []),
             $config
         );
@@ -96,18 +91,18 @@ class Server
         if ($realIpHeader) {
             $remote = $request->getHeader($realIpHeader);
             if (!$remote) {
-                GOTO DIRECT;
+                goto DIRECT;
             }
-            $tmp = explode(',', $remote);
-            $remote = trim(end($tmp));
+            $tmp = \explode(',', $remote);
+            $remote = \trim(\end($tmp));
         } else {
             DIRECT:
             $remote = $request->getClient()->getRemoteAddress()->toString();
-            $hostArray = explode(':', $remote);
-            if (count($hostArray) >= 2) {
-                $port = (int)array_pop($hostArray);
+            $hostArray = \explode(':', $remote);
+            if (\count($hostArray) >= 2) {
+                $port = (int) \array_pop($hostArray);
                 if ($port > 0 && $port <= 65353) {
-                    $remote = implode(':', $hostArray);
+                    $remote = \implode(':', $hostArray);
                 }
             }
 
@@ -116,4 +111,4 @@ class Server
         return $remote;
     }
 
-}
\ No newline at end of file
+}