diff --git a/README.md b/README.md index 00435246..a5ba82b3 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,11 @@ sudo php ./php-profiler inspector:trace -p sudo php ./php-profiler inspector:eg -p ``` +## Use daemon mode +``` +sudo php ./php-profiler inspector:daemon -P +``` + ## Use in a docker container and target a process on host ``` docker build -t php-profiler . @@ -77,4 +82,4 @@ This product includes the Zend Engine, freely available at ``` # Credits -- php-profiler is heavily inspired by [adsr/phpspy](https://github.com/adsr/phpspy). \ No newline at end of file +- php-profiler is heavily inspired by [adsr/phpspy](https://github.com/adsr/phpspy). diff --git a/composer.json b/composer.json index fc052927..0d06981e 100644 --- a/composer.json +++ b/composer.json @@ -20,8 +20,11 @@ "php": "^7.4", "ext-ffi": "*", "ext-pcntl": "*", + "ext-filter": "*", "symfony/console": "^5.0", - "php-di/php-di": "^6.1" + "php-di/php-di": "^6.1", + "amphp/parallel": "^1.4", + "amphp/amp": "^2.4" }, "require-dev": { "ext-posix": "*", diff --git a/composer.lock b/composer.lock index 737a0222..4db5aeea 100644 --- a/composer.lock +++ b/composer.lock @@ -4,40 +4,124 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ae33faf31d016e2ee04b31555c5573c4", + "content-hash": "063825ed6edb78b5dc63a482bf0de643", "packages": [ { - "name": "jeremeamia/superclosure", - "version": "2.4.0", + "name": "amphp/amp", + "version": "v2.4.4", "source": { "type": "git", - "url": "https://github.com/jeremeamia/super_closure.git", - "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9" + "url": "https://github.com/amphp/amp.git", + "reference": "1e58d53e4af390efc7813e36cd215bd82cba4b06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/jeremeamia/super_closure/zipball/5707d5821b30b9a07acfb4d76949784aaa0e9ce9", - "reference": "5707d5821b30b9a07acfb4d76949784aaa0e9ce9", + "url": "https://api.github.com/repos/amphp/amp/zipball/1e58d53e4af390efc7813e36cd215bd82cba4b06", + "reference": "1e58d53e4af390efc7813e36cd215bd82cba4b06", "shasum": "" }, "require": { - "nikic/php-parser": "^1.2|^2.0|^3.0|^4.0", - "php": ">=5.4", - "symfony/polyfill-php56": "^1.0" + "php": ">=7" }, "require-dev": { - "phpunit/phpunit": "^4.0|^5.0" + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6.0.9 | ^7", + "react/promise": "^2", + "vimeo/psalm": "^3.11@dev" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.4-dev" + "dev-master": "2.x-dev" } }, "autoload": { "psr-4": { - "SuperClosure\\": "src/" + "Amp\\": "lib" + }, + "files": [ + "lib/functions.php", + "lib/Internal/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Daniel Lowrey", + "email": "rdlowrey@php.net" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } + ], + "description": "A non-blocking concurrency framework for PHP applications.", + "homepage": "http://amphp.org/amp", + "keywords": [ + "async", + "asynchronous", + "awaitable", + "concurrency", + "event", + "event-loop", + "future", + "non-blocking", + "promise" + ], + "time": "2020-04-30T04:54:50+00:00" + }, + { + "name": "amphp/byte-stream", + "version": "v1.7.3", + "source": { + "type": "git", + "url": "https://github.com/amphp/byte-stream.git", + "reference": "b867505edb79dda8f253ca3c3a2bbadae4b16592" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/b867505edb79dda8f253ca3c3a2bbadae4b16592", + "reference": "b867505edb79dda8f253ca3c3a2bbadae4b16592", + "shasum": "" + }, + "require": { + "amphp/amp": "^2" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "friendsofphp/php-cs-fixer": "^2.3", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^6 || ^7 || ^8", + "vimeo/psalm": "^3.9@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Amp\\ByteStream\\": "lib" + }, + "files": [ + "lib/functions.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -45,77 +129,368 @@ ], "authors": [ { - "name": "Jeremy Lindblom", - "email": "jeremeamia@gmail.com", - "homepage": "https://github.com/jeremeamia", - "role": "Developer" + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "Serialize Closure objects, including their context and binding", - "homepage": "https://github.com/jeremeamia/super_closure", + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "http://amphp.org/byte-stream", "keywords": [ - "closure", - "function", - "lambda", + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" + ], + "time": "2020-04-04T16:56:54+00:00" + }, + { + "name": "amphp/parallel", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/parallel.git", + "reference": "2c1039bf7ca137eae4d954b14c09a7535d7d4e1c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parallel/zipball/2c1039bf7ca137eae4d954b14c09a7535d7d4e1c", + "reference": "2c1039bf7ca137eae4d954b14c09a7535d7d4e1c", + "shasum": "" + }, + "require": { + "amphp/amp": "^2", + "amphp/byte-stream": "^1.6.1", + "amphp/parser": "^1", + "amphp/process": "^1", + "amphp/serialization": "^1", + "amphp/sync": "^1.0.1", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.1", + "phpunit/phpunit": "^8 || ^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parallel\\": "lib" + }, + "files": [ + "lib/Context/functions.php", + "lib/Sync/functions.php", + "lib/Worker/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Parallel processing component for Amp.", + "homepage": "https://github.com/amphp/parallel", + "keywords": [ + "async", + "asynchronous", + "concurrent", + "multi-processing", + "multi-threading" + ], + "time": "2020-04-27T15:12:37+00:00" + }, + { + "name": "amphp/parser", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/parser.git", + "reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/parser/zipball/f83e68f03d5b8e8e0365b8792985a7f341c57ae1", + "reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1", + "shasum": "" + }, + "require": { + "php": ">=7" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.3", + "phpunit/phpunit": "^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Parser\\": "lib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "A generator parser to make streaming parsers simple.", + "homepage": "https://github.com/amphp/parser", + "keywords": [ + "async", + "non-blocking", "parser", - "serializable", - "serialize", - "tokenizer" + "stream" ], - "abandoned": "opis/closure", - "time": "2018-03-21T22:21:57+00:00" + "time": "2017-06-06T05:29:10+00:00" }, { - "name": "nikic/php-parser", - "version": "v4.4.0", + "name": "amphp/process", + "version": "v1.1.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120" + "url": "https://github.com/amphp/process.git", + "reference": "355b1e561b01c16ab3d78fada1ad47ccc96df70e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120", - "reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120", + "url": "https://api.github.com/repos/amphp/process/zipball/355b1e561b01c16ab3d78fada1ad47ccc96df70e", + "reference": "355b1e561b01c16ab3d78fada1ad47ccc96df70e", "shasum": "" }, "require": { - "ext-tokenizer": "*", - "php": ">=7.0" + "amphp/amp": "^2", + "amphp/byte-stream": "^1.4", + "php": ">=7" }, "require-dev": { - "ircmaxell/php-yacc": "0.0.5", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1", + "phpunit/phpunit": "^6" }, - "bin": [ - "bin/php-parse" + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Process\\": "lib" + }, + "files": [ + "lib/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" ], + "authors": [ + { + "name": "Bob Weinand", + "email": "bobwei9@hotmail.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + }, + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + } + ], + "description": "Asynchronous process manager.", + "homepage": "https://github.com/amphp/process", + "time": "2019-02-26T16:33:03+00:00" + }, + { + "name": "amphp/serialization", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/serialization.git", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/serialization/zipball/693e77b2fb0b266c3c7d622317f881de44ae94a1", + "reference": "693e77b2fb0b266c3c7d622317f881de44ae94a1", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Serialization\\": "src" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Serialization tools for IPC and data storage in PHP.", + "homepage": "https://github.com/amphp/serialization", + "keywords": [ + "async", + "asynchronous", + "serialization", + "serialize" + ], + "time": "2020-03-25T21:39:07+00:00" + }, + { + "name": "amphp/sync", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/amphp/sync.git", + "reference": "613047ac54c025aa800a9cde5b05c3add7327ed4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/amphp/sync/zipball/613047ac54c025aa800a9cde5b05c3add7327ed4", + "reference": "613047ac54c025aa800a9cde5b05c3add7327ed4", + "shasum": "" + }, + "require": { + "amphp/amp": "^2.2", + "php": ">=7.1" + }, + "require-dev": { + "amphp/php-cs-fixer-config": "dev-master", + "amphp/phpunit-util": "^1.1", + "phpunit/phpunit": "^9 || ^8 || ^7" + }, + "type": "library", + "autoload": { + "psr-4": { + "Amp\\Sync\\": "src" + }, + "files": [ + "src/functions.php", + "src/ConcurrentIterator/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Stephen Coakley", + "email": "me@stephencoakley.com" + } + ], + "description": "Mutex, Semaphore, and other synchronization tools for Amp.", + "homepage": "https://github.com/amphp/sync", + "keywords": [ + "async", + "asynchronous", + "mutex", + "semaphore", + "synchronization" + ], + "time": "2020-05-07T18:57:50+00:00" + }, + { + "name": "opis/closure", + "version": "3.5.5", + "source": { + "type": "git", + "url": "https://github.com/opis/closure.git", + "reference": "dec9fc5ecfca93f45cd6121f8e6f14457dff372c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opis/closure/zipball/dec9fc5ecfca93f45cd6121f8e6f14457dff372c", + "reference": "dec9fc5ecfca93f45cd6121f8e6f14457dff372c", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0" + }, + "require-dev": { + "jeremeamia/superclosure": "^2.0", + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.3-dev" + "dev-master": "3.5.x-dev" } }, "autoload": { "psr-4": { - "PhpParser\\": "lib/PhpParser" - } + "Opis\\Closure\\": "src/" + }, + "files": [ + "functions.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], "authors": [ { - "name": "Nikita Popov" + "name": "Marius Sarca", + "email": "marius.sarca@gmail.com" + }, + { + "name": "Sorin Sarca", + "email": "sarca_sorin@hotmail.com" } ], - "description": "A PHP parser written in PHP", + "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", + "homepage": "https://opis.io/closure", "keywords": [ - "parser", - "php" + "anonymous functions", + "closure", + "function", + "serializable", + "serialization", + "serialize" ], - "time": "2020-04-10T16:34:50+00:00" + "time": "2020-06-17T14:59:55+00:00" }, { "name": "php-di/invoker", @@ -162,21 +537,20 @@ }, { "name": "php-di/php-di", - "version": "6.1.0", + "version": "6.2.1", "source": { "type": "git", "url": "https://github.com/PHP-DI/PHP-DI.git", - "reference": "69238bd49acc0eb6a967029311eeadc3f7c5d538" + "reference": "6875fe557c244b3830862c072c7719ca4ac2efe4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/69238bd49acc0eb6a967029311eeadc3f7c5d538", - "reference": "69238bd49acc0eb6a967029311eeadc3f7c5d538", + "url": "https://api.github.com/repos/PHP-DI/PHP-DI/zipball/6875fe557c244b3830862c072c7719ca4ac2efe4", + "reference": "6875fe557c244b3830862c072c7719ca4ac2efe4", "shasum": "" }, "require": { - "jeremeamia/superclosure": "^2.0", - "nikic/php-parser": "^2.0|^3.0|^4.0", + "opis/closure": "^3.5.5", "php": ">=7.2.0", "php-di/invoker": "^2.0", "php-di/phpdoc-reader": "^2.0.1", @@ -211,7 +585,7 @@ "MIT" ], "description": "The dependency injection container for humans", - "homepage": "http://php-di.org/", + "homepage": "https://php-di.org/", "keywords": [ "PSR-11", "container", @@ -221,7 +595,7 @@ "ioc", "psr11" ], - "time": "2020-04-06T09:54:49+00:00" + "time": "2020-06-18T09:54:32+00:00" }, { "name": "php-di/phpdoc-reader", @@ -311,26 +685,29 @@ }, { "name": "symfony/console", - "version": "v5.0.7", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935" + "reference": "34ac555a3627e324b660e318daa07572e1140123" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/5fa1caadc8cdaa17bcfb25219f3b53fe294a9935", - "reference": "5fa1caadc8cdaa17bcfb25219f3b53fe294a9935", + "url": "https://api.github.com/repos/symfony/console/zipball/34ac555a3627e324b660e318daa07572e1140123", + "reference": "34ac555a3627e324b660e318daa07572e1140123", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-php73": "^1.8", - "symfony/service-contracts": "^1.1|^2" + "symfony/polyfill-php80": "^1.15", + "symfony/service-contracts": "^1.1|^2", + "symfony/string": "^5.1" }, "conflict": { "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", "symfony/event-dispatcher": "<4.4", "symfony/lock": "<4.4", "symfony/process": "<4.4" @@ -356,7 +733,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -383,37 +760,37 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2020-03-30T11:42:42+00:00" + "time": "2020-06-15T12:59:21+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.15.0", + "name": "symfony/polyfill-ctype", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", - "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", "shasum": "" }, "require": { "php": ">=5.3.3" }, "suggest": { - "ext-mbstring": "For best performance" + "ext-ctype": "For best performance" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" + "Symfony\\Polyfill\\Ctype\\": "" }, "files": [ "bootstrap.php" @@ -425,42 +802,43 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", + "ctype", "polyfill", - "portable", - "shim" + "portable" ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-12T16:14:59+00:00" }, { - "name": "symfony/polyfill-php56", + "name": "symfony/polyfill-intl-grapheme", "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "e3c8c138280cdfe4b81488441555583aa1984e23" + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "e094b0770f7833fdf257e6ba4775be4e258230b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e3c8c138280cdfe4b81488441555583aa1984e23", - "reference": "e3c8c138280cdfe4b81488441555583aa1984e23", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/e094b0770f7833fdf257e6ba4775be4e258230b2", + "reference": "e094b0770f7833fdf257e6ba4775be4e258230b2", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-util": "~1.0" + "php": ">=5.3.3" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { @@ -470,7 +848,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php56\\": "" + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" }, "files": [ "bootstrap.php" @@ -490,10 +868,12 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 5.6+ features to lower PHP versions", + "description": "Symfony polyfill for intl's grapheme_* functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "grapheme", + "intl", "polyfill", "portable", "shim" @@ -501,31 +881,34 @@ "time": "2020-05-12T16:47:27+00:00" }, { - "name": "symfony/polyfill-php73", - "version": "v1.15.0", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", - "reference": "0f27e9f464ea3da33cbe7ca3bdf4eb66def9d0f7", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/1357b1d168eb7f68ad6a134838e46b0b159444a9", + "reference": "1357b1d168eb7f68ad6a134838e46b0b159444a9", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-intl": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Php73\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, "files": [ "bootstrap.php" @@ -548,33 +931,38 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ "compatibility", + "intl", + "normalizer", "polyfill", "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:14:59+00:00" }, { - "name": "symfony/polyfill-util", + "name": "symfony/polyfill-mbstring", "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "4afb4110fc037752cf0ce9869f9ab8162c4e20d7" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4afb4110fc037752cf0ce9869f9ab8162c4e20d7", - "reference": "4afb4110fc037752cf0ce9869f9ab8162c4e20d7", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-mbstring": "For best performance" + }, "type": "library", "extra": { "branch-alias": { @@ -583,8 +971,11 @@ }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Util\\": "" - } + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -600,47 +991,50 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony utilities for portability of PHP codes", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ - "compat", "compatibility", + "mbstring", "polyfill", + "portable", "shim" ], - "time": "2020-05-12T16:14:59+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.0.1", + "name": "symfony/polyfill-php73", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "144c5e51266b281231e947b51223ba14acf1a749" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", - "reference": "144c5e51266b281231e947b51223ba14acf1a749", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/a760d8964ff79ab9bf057613a5808284ec852ccc", + "reference": "a760d8964ff79ab9bf057613a5808284ec852ccc", "shasum": "" }, "require": { - "php": "^7.2.5", - "psr/container": "^1.0" - }, - "suggest": { - "symfony/service-implementation": "" + "php": ">=5.3.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "1.17-dev" } }, "autoload": { "psr-4": { - "Symfony\\Contracts\\Service\\": "" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -656,59 +1050,48 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "polyfill", + "portable", + "shim" ], - "time": "2019-11-18T17:27:11+00:00" - } - ], - "packages-dev": [ + "time": "2020-05-12T16:47:27+00:00" + }, { - "name": "amphp/amp", - "version": "v2.4.2", + "name": "symfony/polyfill-php80", + "version": "v1.17.0", "source": { "type": "git", - "url": "https://github.com/amphp/amp.git", - "reference": "feca077369a47263b22156b3c6389e55f3809f24" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/feca077369a47263b22156b3c6389e55f3809f24", - "reference": "feca077369a47263b22156b3c6389e55f3809f24", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/5e30b2799bc1ad68f7feb62b60a73743589438dd", + "reference": "5e30b2799bc1ad68f7feb62b60a73743589438dd", "shasum": "" }, "require": { - "php": ">=7" - }, - "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "ext-json": "*", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6.0.9 | ^7", - "react/promise": "^2", - "vimeo/psalm": "^3.9@dev" + "php": ">=7.0.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.x-dev" + "dev-master": "1.17-dev" } }, "autoload": { "psr-4": { - "Amp\\": "lib" + "Symfony\\Polyfill\\Php80\\": "" }, "files": [ - "lib/functions.php", - "lib/Internal/functions.php" + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -717,74 +1100,129 @@ ], "authors": [ { - "name": "Daniel Lowrey", - "email": "rdlowrey@php.net" + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" }, { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Bob Weinand", - "email": "bobwei9@hotmail.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" + ], + "time": "2020-05-12T16:47:27+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v2.1.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Niklas Keller", - "email": "me@kelunik.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A non-blocking concurrency framework for PHP applications.", - "homepage": "http://amphp.org/amp", + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", "keywords": [ - "async", - "asynchronous", - "awaitable", - "concurrency", - "event", - "event-loop", - "future", - "non-blocking", - "promise" + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" ], - "time": "2020-04-04T15:05:26+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { - "name": "amphp/byte-stream", - "version": "v1.7.3", + "name": "symfony/string", + "version": "v5.1.2", "source": { "type": "git", - "url": "https://github.com/amphp/byte-stream.git", - "reference": "b867505edb79dda8f253ca3c3a2bbadae4b16592" + "url": "https://github.com/symfony/string.git", + "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/b867505edb79dda8f253ca3c3a2bbadae4b16592", - "reference": "b867505edb79dda8f253ca3c3a2bbadae4b16592", + "url": "https://api.github.com/repos/symfony/string/zipball/ac70459db781108db7c6d8981dd31ce0e29e3298", + "reference": "ac70459db781108db7c6d8981dd31ce0e29e3298", "shasum": "" }, "require": { - "amphp/amp": "^2" + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php80": "~1.15" }, "require-dev": { - "amphp/php-cs-fixer-config": "dev-master", - "amphp/phpunit-util": "^1", - "friendsofphp/php-cs-fixer": "^2.3", - "jetbrains/phpstorm-stubs": "^2019.3", - "phpunit/phpunit": "^6 || ^7 || ^8", - "vimeo/psalm": "^3.9@dev" + "symfony/error-handler": "^4.4|^5.0", + "symfony/http-client": "^4.4|^5.0", + "symfony/translation-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.4|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "5.1-dev" } }, "autoload": { "psr-4": { - "Amp\\ByteStream\\": "lib" + "Symfony\\Component\\String\\": "" }, "files": [ - "lib/functions.php" + "Resources/functions.php" + ], + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -793,50 +1231,53 @@ ], "authors": [ { - "name": "Aaron Piotrowski", - "email": "aaron@trowski.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { - "name": "Niklas Keller", - "email": "me@kelunik.com" + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", + "description": "Symfony String component", + "homepage": "https://symfony.com", "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" - ], - "time": "2020-04-04T16:56:54+00:00" - }, + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "time": "2020-06-11T12:16:36+00:00" + } + ], + "packages-dev": [ { "name": "composer/semver", - "version": "1.5.1", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de" + "reference": "3426bd5efa8a12d230824536c42a8a4ad30b7940" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c6bea70230ef4dd483e6bbcab6005f682ed3a8de", - "reference": "c6bea70230ef4dd483e6bbcab6005f682ed3a8de", + "url": "https://api.github.com/repos/composer/semver/zipball/3426bd5efa8a12d230824536c42a8a4ad30b7940", + "reference": "3426bd5efa8a12d230824536c42a8a4ad30b7940", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.5 || ^5.0.5" + "phpstan/phpstan": "^0.12.19", + "symfony/phpunit-bridge": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "3.x-dev" } }, "autoload": { @@ -872,20 +1313,20 @@ "validation", "versioning" ], - "time": "2020-01-13T12:06:48+00:00" + "time": "2020-05-26T18:22:04+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.4.1", + "version": "1.4.2", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7" + "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/1ab9842d69e64fb3a01be6b656501032d1b78cb7", - "reference": "1ab9842d69e64fb3a01be6b656501032d1b78cb7", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", + "reference": "fa2aaf99e2087f013a14f7432c1cd2dd7d8f1f51", "shasum": "" }, "require": { @@ -916,24 +1357,24 @@ "Xdebug", "performance" ], - "time": "2020-03-01T12:26:26+00:00" + "time": "2020-06-04T11:16:35+00:00" }, { "name": "doctrine/instantiator", - "version": "1.3.0", + "version": "1.3.1", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", - "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/f350df0268e904597e3bd9c4685c53e0e333feea", + "reference": "f350df0268e904597e3bd9c4685c53e0e333feea", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^6.0", @@ -972,7 +1413,7 @@ "constructor", "instantiate" ], - "time": "2019-10-21T16:45:58+00:00" + "time": "2020-05-29T17:27:14+00:00" }, { "name": "felixfbecker/advanced-json-rpc", @@ -1064,16 +1505,16 @@ }, { "name": "guzzlehttp/guzzle", - "version": "6.5.3", + "version": "6.5.5", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e" + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e", - "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", + "reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e", "shasum": "" }, "require": { @@ -1081,7 +1522,7 @@ "guzzlehttp/promises": "^1.0", "guzzlehttp/psr7": "^1.6.1", "php": ">=5.5", - "symfony/polyfill-intl-idn": "^1.11" + "symfony/polyfill-intl-idn": "^1.17.0" }, "require-dev": { "ext-curl": "*", @@ -1127,7 +1568,7 @@ "rest", "web service" ], - "time": "2020-04-18T10:38:46+00:00" + "time": "2020-06-16T21:01:06+00:00" }, { "name": "guzzlehttp/promises", @@ -1305,12 +1746,12 @@ "source": { "type": "git", "url": "https://github.com/JetBrains/phpstorm-stubs.git", - "reference": "2d61fb6210e3207622b6db90cb174a1ed6c5949f" + "reference": "4c85d8c07f05e6737d04d1346a2b2e4c8509171d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/2d61fb6210e3207622b6db90cb174a1ed6c5949f", - "reference": "2d61fb6210e3207622b6db90cb174a1ed6c5949f", + "url": "https://api.github.com/repos/JetBrains/phpstorm-stubs/zipball/4c85d8c07f05e6737d04d1346a2b2e4c8509171d", + "reference": "4c85d8c07f05e6737d04d1346a2b2e4c8509171d", "shasum": "" }, "require-dev": { @@ -1341,34 +1782,37 @@ "stubs", "type" ], - "time": "2020-05-06T09:39:59+00:00" + "time": "2020-05-28T11:48:35+00:00" }, { "name": "mockery/mockery", - "version": "1.3.1", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be" + "reference": "6c6a7c533469873deacf998237e7649fc6b36223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", - "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", + "url": "https://api.github.com/repos/mockery/mockery/zipball/6c6a7c533469873deacf998237e7649fc6b36223", + "reference": "6c6a7c533469873deacf998237e7649fc6b36223", "shasum": "" }, "require": { "hamcrest/hamcrest-php": "~2.0", "lib-pcre": ">=7.0", - "php": ">=5.6.0" + "php": "^7.3.0" + }, + "conflict": { + "phpunit/phpunit": "<8.0" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" + "phpunit/phpunit": "^8.0.0 || ^9.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.4.x-dev" } }, "autoload": { @@ -1406,7 +1850,7 @@ "test double", "testing" ], - "time": "2019-12-26T09:49:15+00:00" + "time": "2020-05-19T14:25:16+00:00" }, { "name": "myclabs/deep-copy", @@ -1502,6 +1946,58 @@ "description": "Map nested JSON structures onto PHP classes", "time": "2020-04-16T18:48:43+00:00" }, + { + "name": "nikic/php-parser", + "version": "v4.5.0", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/53c2753d756f5adb586dca79c2ec0e2654dd9463", + "reference": "53c2753d756f5adb586dca79c2ec0e2654dd9463", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "ircmaxell/php-yacc": "0.0.5", + "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2020-06-03T07:24:19+00:00" + }, { "name": "ocramius/package-versions", "version": "1.8.0", @@ -1789,24 +2285,21 @@ }, { "name": "phpdocumentor/reflection-common", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", - "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/6568f4687e5b41b054365f9ae03fcb1ed5f2069b", + "reference": "6568f4687e5b41b054365f9ae03fcb1ed5f2069b", "shasum": "" }, "require": { "php": ">=7.1" }, - "require-dev": { - "phpunit/phpunit": "~6" - }, "type": "library", "extra": { "branch-alias": { @@ -1837,45 +2330,42 @@ "reflection", "static analysis" ], - "time": "2018-08-07T13:53:10+00:00" + "time": "2020-04-27T09:25:28+00:00" }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.4", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c" + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c", - "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", + "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", "shasum": "" }, "require": { - "php": "^7.0", - "phpdocumentor/reflection-common": "^1.0.0 || ^2.0.0", - "phpdocumentor/type-resolver": "~0.4 || ^1.0.0", - "webmozart/assert": "^1.0" + "ext-filter": "^7.1", + "php": "^7.2", + "phpdocumentor/reflection-common": "^2.0", + "phpdocumentor/type-resolver": "^1.0", + "webmozart/assert": "^1" }, "require-dev": { - "doctrine/instantiator": "^1.0.5", - "mockery/mockery": "^1.0", - "phpdocumentor/type-resolver": "0.4.*", - "phpunit/phpunit": "^6.4" + "doctrine/instantiator": "^1", + "mockery/mockery": "^1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.x-dev" + "dev-master": "5.x-dev" } }, "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] + "phpDocumentor\\Reflection\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1886,10 +2376,14 @@ { "name": "Mike van Riel", "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "account@ijaap.nl" } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2019-12-28T18:55:12+00:00" + "time": "2020-02-22T12:28:44+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1939,16 +2433,16 @@ }, { "name": "phpspec/prophecy", - "version": "v1.10.2", + "version": "v1.10.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9" + "reference": "451c3cd1418cf640de218914901e51b064abb093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9", - "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", "shasum": "" }, "require": { @@ -1998,20 +2492,20 @@ "spy", "stub" ], - "time": "2020-01-20T15:57:02+00:00" + "time": "2020-03-05T15:02:03+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "8.0.1", + "version": "8.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "31e94ccc084025d6abee0585df533eb3a792b96a" + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/31e94ccc084025d6abee0585df533eb3a792b96a", - "reference": "31e94ccc084025d6abee0585df533eb3a792b96a", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca6647ffddd2add025ab3f21644a441d7c146cdc", + "reference": "ca6647ffddd2add025ab3f21644a441d7c146cdc", "shasum": "" }, "require": { @@ -2062,20 +2556,20 @@ "testing", "xunit" ], - "time": "2020-02-19T13:41:19+00:00" + "time": "2020-05-23T08:02:54+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.0", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "354d4a5faa7449a377a18b94a2026ca3415e3d7a" + "reference": "eba15e538f2bb3fe018b7bbb47d2fe32d404bfd2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/354d4a5faa7449a377a18b94a2026ca3415e3d7a", - "reference": "354d4a5faa7449a377a18b94a2026ca3415e3d7a", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/eba15e538f2bb3fe018b7bbb47d2fe32d404bfd2", + "reference": "eba15e538f2bb3fe018b7bbb47d2fe32d404bfd2", "shasum": "" }, "require": { @@ -2112,20 +2606,20 @@ "filesystem", "iterator" ], - "time": "2020-02-07T06:05:22+00:00" + "time": "2020-06-15T12:54:35+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a" + "reference": "62f696ad0d140e0e513e69eaafdebb674d622b4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/7579d5a1ba7f3ac11c80004d205877911315ae7a", - "reference": "7579d5a1ba7f3ac11c80004d205877911315ae7a", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/62f696ad0d140e0e513e69eaafdebb674d622b4c", + "reference": "62f696ad0d140e0e513e69eaafdebb674d622b4c", "shasum": "" }, "require": { @@ -2165,25 +2659,28 @@ "keywords": [ "process" ], - "time": "2020-02-07T06:06:11+00:00" + "time": "2020-06-15T13:10:07+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346" + "reference": "0c69cbf965d5317ba33f24a352539f354a25db09" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/526dc996cc0ebdfa428cd2dfccd79b7b53fee346", - "reference": "526dc996cc0ebdfa428cd2dfccd79b7b53fee346", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c69cbf965d5317ba33f24a352539f354a25db09", + "reference": "0c69cbf965d5317ba33f24a352539f354a25db09", "shasum": "" }, "require": { "php": "^7.3" }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, "type": "library", "extra": { "branch-alias": { @@ -2211,32 +2708,32 @@ "keywords": [ "template" ], - "time": "2020-02-01T07:43:44+00:00" + "time": "2020-06-15T12:52:43+00:00" }, { "name": "phpunit/php-timer", - "version": "3.0.0", + "version": "5.0.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "4118013a4d0f97356eae8e7fb2f6c6472575d1df" + "reference": "b0d089de001ba60ffa3be36b23e1b8150d072238" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/4118013a4d0f97356eae8e7fb2f6c6472575d1df", - "reference": "4118013a4d0f97356eae8e7fb2f6c6472575d1df", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/b0d089de001ba60ffa3be36b23e1b8150d072238", + "reference": "b0d089de001ba60ffa3be36b23e1b8150d072238", "shasum": "" }, "require": { "php": "^7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -2260,20 +2757,20 @@ "keywords": [ "timer" ], - "time": "2020-02-07T06:08:11+00:00" + "time": "2020-06-07T12:05:53+00:00" }, { "name": "phpunit/php-token-stream", - "version": "4.0.0", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "b2560a0c33f7710e4d7f8780964193e8e8f8effe" + "reference": "e61c593e9734b47ef462340c24fca8d6a57da14e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/b2560a0c33f7710e4d7f8780964193e8e8f8effe", - "reference": "b2560a0c33f7710e4d7f8780964193e8e8f8effe", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e61c593e9734b47ef462340c24fca8d6a57da14e", + "reference": "e61c593e9734b47ef462340c24fca8d6a57da14e", "shasum": "" }, "require": { @@ -2309,20 +2806,20 @@ "keywords": [ "tokenizer" ], - "time": "2020-02-07T06:19:00+00:00" + "time": "2020-06-16T07:00:44+00:00" }, { "name": "phpunit/phpunit", - "version": "9.0.1", + "version": "9.2.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "68d7e5b17a6b9461e17c00446caa409863154f76" + "reference": "c1b1d62095ef78427f112a7a1c1502d4607e3c00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/68d7e5b17a6b9461e17c00446caa409863154f76", - "reference": "68d7e5b17a6b9461e17c00446caa409863154f76", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1b1d62095ef78427f112a7a1c1502d4607e3c00", + "reference": "c1b1d62095ef78427f112a7a1c1502d4607e3c00", "shasum": "" }, "require": { @@ -2338,23 +2835,25 @@ "phar-io/version": "^2.0.1", "php": "^7.3", "phpspec/prophecy": "^1.8.1", - "phpunit/php-code-coverage": "^8.0", + "phpunit/php-code-coverage": "^8.0.1", "phpunit/php-file-iterator": "^3.0", "phpunit/php-invoker": "^3.0", "phpunit/php-text-template": "^2.0", - "phpunit/php-timer": "^3.0", + "phpunit/php-timer": "^5.0", + "sebastian/code-unit": "^1.0.2", "sebastian/comparator": "^4.0", "sebastian/diff": "^4.0", - "sebastian/environment": "^5.0", + "sebastian/environment": "^5.0.1", "sebastian/exporter": "^4.0", "sebastian/global-state": "^4.0", "sebastian/object-enumerator": "^4.0", "sebastian/resource-operations": "^3.0", - "sebastian/type": "^2.0", + "sebastian/type": "^2.1", "sebastian/version": "^3.0" }, "require-dev": { - "ext-pdo": "*" + "ext-pdo": "*", + "phpspec/prophecy-phpunit": "^2.0" }, "suggest": { "ext-soap": "*", @@ -2366,7 +2865,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.0-dev" + "dev-master": "9.2-dev" } }, "autoload": { @@ -2395,7 +2894,7 @@ "testing", "xunit" ], - "time": "2020-02-13T07:30:12+00:00" + "time": "2020-06-15T10:51:34+00:00" }, { "name": "psalm/plugin-mockery", @@ -2585,18 +3084,64 @@ "description": "A polyfill for getallheaders.", "time": "2019-03-08T08:55:37+00:00" }, + { + "name": "sebastian/code-unit", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit.git", + "reference": "d650ef9b1fece15ed4d6eaed6e6b469b7b81183a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/d650ef9b1fece15ed4d6eaed6e6b469b7b81183a", + "reference": "d650ef9b1fece15ed4d6eaed6e6b469b7b81183a", + "shasum": "" + }, + "require": { + "php": "^7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Collection of value objects that represent the PHP code units", + "homepage": "https://github.com/sebastianbergmann/code-unit", + "time": "2020-06-15T13:11:26+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e" + "reference": "c771130f0e8669104a4320b7101a81c2cc2963ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5b5dbe0044085ac41df47e79d34911a15b96d82e", - "reference": "5b5dbe0044085ac41df47e79d34911a15b96d82e", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c771130f0e8669104a4320b7101a81c2cc2963ef", + "reference": "c771130f0e8669104a4320b7101a81c2cc2963ef", "shasum": "" }, "require": { @@ -2628,20 +3173,20 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2020-02-07T06:20:13+00:00" + "time": "2020-06-15T12:56:39+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.0", + "version": "4.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8" + "reference": "266d85ef789da8c41f06af4093c43e9798af2784" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/85b3435da967696ed618ff745f32be3ff4a2b8e8", - "reference": "85b3435da967696ed618ff745f32be3ff4a2b8e8", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/266d85ef789da8c41f06af4093c43e9798af2784", + "reference": "266d85ef789da8c41f06af4093c43e9798af2784", "shasum": "" }, "require": { @@ -2692,20 +3237,20 @@ "compare", "equality" ], - "time": "2020-02-07T06:08:51+00:00" + "time": "2020-06-15T15:04:48+00:00" }, { "name": "sebastian/diff", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c0c26c9188b538bfa985ae10c9f05d278f12060d" + "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c0c26c9188b538bfa985ae10c9f05d278f12060d", - "reference": "c0c26c9188b538bfa985ae10c9f05d278f12060d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3e523c576f29dacecff309f35e4cc5a5c168e78a", + "reference": "3e523c576f29dacecff309f35e4cc5a5c168e78a", "shasum": "" }, "require": { @@ -2713,7 +3258,7 @@ }, "require-dev": { "phpunit/phpunit": "^9.0", - "symfony/process": "^4 || ^5" + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { @@ -2748,20 +3293,20 @@ "unidiff", "unified diff" ], - "time": "2020-02-07T06:09:38+00:00" + "time": "2020-05-08T05:01:12+00:00" }, { "name": "sebastian/environment", - "version": "5.0.1", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "9bffdefa7810031a165ddd6275da6a2c1f6f2dfb" + "reference": "16eb0fa43e29c33d7f2117ed23072e26fc5ab34e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/9bffdefa7810031a165ddd6275da6a2c1f6f2dfb", - "reference": "9bffdefa7810031a165ddd6275da6a2c1f6f2dfb", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/16eb0fa43e29c33d7f2117ed23072e26fc5ab34e", + "reference": "16eb0fa43e29c33d7f2117ed23072e26fc5ab34e", "shasum": "" }, "require": { @@ -2801,20 +3346,20 @@ "environment", "hhvm" ], - "time": "2020-02-19T13:40:20+00:00" + "time": "2020-06-15T13:00:01+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "80c26562e964016538f832f305b2286e1ec29566" + "reference": "d12fbca85da932d01d941b59e4b71a0d559db091" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/80c26562e964016538f832f305b2286e1ec29566", - "reference": "80c26562e964016538f832f305b2286e1ec29566", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/d12fbca85da932d01d941b59e4b71a0d559db091", + "reference": "d12fbca85da932d01d941b59e4b71a0d559db091", "shasum": "" }, "require": { @@ -2868,7 +3413,7 @@ "export", "exporter" ], - "time": "2020-02-07T06:10:52+00:00" + "time": "2020-06-15T13:12:44+00:00" }, { "name": "sebastian/global-state", @@ -2926,16 +3471,16 @@ }, { "name": "sebastian/object-enumerator", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "e67516b175550abad905dc952f43285957ef4363" + "reference": "15f319d67c49fc55ebcdbffb3377433125588455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/e67516b175550abad905dc952f43285957ef4363", - "reference": "e67516b175550abad905dc952f43285957ef4363", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/15f319d67c49fc55ebcdbffb3377433125588455", + "reference": "15f319d67c49fc55ebcdbffb3377433125588455", "shasum": "" }, "require": { @@ -2969,20 +3514,20 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2020-02-07T06:12:23+00:00" + "time": "2020-06-15T13:15:25+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7" + "reference": "14e04b3c25b821cc0702d4837803fe497680b062" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", - "reference": "f4fd0835cabb0d4a6546d9fe291e5740037aa1e7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/14e04b3c25b821cc0702d4837803fe497680b062", + "reference": "14e04b3c25b821cc0702d4837803fe497680b062", "shasum": "" }, "require": { @@ -3014,20 +3559,20 @@ ], "description": "Allows reflection of object attributes, including inherited and non-public ones", "homepage": "https://github.com/sebastianbergmann/object-reflector/", - "time": "2020-02-07T06:19:40+00:00" + "time": "2020-06-15T13:08:02+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cdd86616411fc3062368b720b0425de10bd3d579" + "reference": "a32789e5f0157c10cf216ce6c5136db12a12b847" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cdd86616411fc3062368b720b0425de10bd3d579", - "reference": "cdd86616411fc3062368b720b0425de10bd3d579", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/a32789e5f0157c10cf216ce6c5136db12a12b847", + "reference": "a32789e5f0157c10cf216ce6c5136db12a12b847", "shasum": "" }, "require": { @@ -3067,20 +3612,20 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2020-02-07T06:18:20+00:00" + "time": "2020-06-15T13:06:44+00:00" }, { "name": "sebastian/resource-operations", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98" + "reference": "71421c1745788de4facae1b79af923650bd3ec15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", - "reference": "8c98bf0dfa1f9256d0468b9803a1e1df31b6fa98", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/71421c1745788de4facae1b79af923650bd3ec15", + "reference": "71421c1745788de4facae1b79af923650bd3ec15", "shasum": "" }, "require": { @@ -3112,32 +3657,32 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2020-02-07T06:13:02+00:00" + "time": "2020-06-15T13:17:14+00:00" }, { "name": "sebastian/type", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "9e8f42f740afdea51f5f4e8cec2035580e797ee1" + "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/9e8f42f740afdea51f5f4e8cec2035580e797ee1", - "reference": "9e8f42f740afdea51f5f4e8cec2035580e797ee1", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", + "reference": "bad49207c6f854e7a25cef0ea948ac8ebe3ef9d8", "shasum": "" }, "require": { "php": "^7.3" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^9.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -3158,7 +3703,7 @@ ], "description": "Collection of value objects that represent the types of the PHP type system", "homepage": "https://github.com/sebastianbergmann/type", - "time": "2020-02-07T06:13:43+00:00" + "time": "2020-06-01T12:21:09+00:00" }, { "name": "sebastian/version", @@ -3256,22 +3801,24 @@ }, { "name": "symfony/config", - "version": "v5.0.8", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "db1674e1a261148429f123871f30d211992294e7" + "reference": "b8623ef3d99fe62a34baf7a111b576216965f880" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/db1674e1a261148429f123871f30d211992294e7", - "reference": "db1674e1a261148429f123871f30d211992294e7", + "url": "https://api.github.com/repos/symfony/config/zipball/b8623ef3d99fe62a34baf7a111b576216965f880", + "reference": "b8623ef3d99fe62a34baf7a111b576216965f880", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/filesystem": "^4.4|^5.0", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.15" }, "conflict": { "symfony/finder": "<4.4" @@ -3289,7 +3836,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3316,38 +3863,34 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2020-04-15T15:59:10+00:00" + "time": "2020-05-23T13:08:13+00:00" }, { - "name": "symfony/filesystem", - "version": "v5.0.8", + "name": "symfony/deprecation-contracts", + "version": "v2.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "7cd0dafc4353a0f62e307df90b48466379c8cc91" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7cd0dafc4353a0f62e307df90b48466379c8cc91", - "reference": "7cd0dafc4353a0f62e307df90b48466379c8cc91", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", + "reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337", "shasum": "" }, "require": { - "php": "^7.2.5", - "symfony/polyfill-ctype": "~1.8" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" + "files": [ + "function.php" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3356,50 +3899,48 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Filesystem Component", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", - "time": "2020-04-12T14:40:17+00:00" + "time": "2020-05-27T08:34:37+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.14.0", + "name": "symfony/filesystem", + "version": "v5.1.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" + "url": "https://github.com/symfony/filesystem.git", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", - "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/6e4320f06d5f2cce0d96530162491f4465179157", + "reference": "6e4320f06d5f2cce0d96530162491f4465179157", "shasum": "" }, "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" + "php": ">=7.2.5", + "symfony/polyfill-ctype": "~1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.14-dev" + "dev-master": "5.1-dev" } }, "autoload": { "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" + "Symfony\\Component\\Filesystem\\": "" }, - "files": [ - "bootstrap.php" + "exclude-from-classmap": [ + "/Tests/" ] }, "notification-url": "https://packagist.org/downloads/", @@ -3408,36 +3949,30 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "time": "2020-01-13T11:15:53+00:00" + "time": "2020-05-30T20:35:19+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", - "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/3bff59ea7047e925be6b7f2059d60af31bb46d6a", + "reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a", "shasum": "" }, "require": { @@ -3451,7 +3986,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -3486,20 +4021,20 @@ "portable", "shim" ], - "time": "2020-03-09T19:04:49+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.15.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "37b0976c78b94856543260ce09b460a7bc852747" + "reference": "f048e612a3905f34931127360bdd2def19a5e582" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", - "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582", + "reference": "f048e612a3905f34931127360bdd2def19a5e582", "shasum": "" }, "require": { @@ -3508,7 +4043,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.15-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -3541,30 +4076,30 @@ "portable", "shim" ], - "time": "2020-02-27T09:26:54+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.0.8", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "a1d86d30d4522423afc998f32404efa34fcf5a73" + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/a1d86d30d4522423afc998f32404efa34fcf5a73", - "reference": "a1d86d30d4522423afc998f32404efa34fcf5a73", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/0f7c58cf81dbb5dd67d423a89d577524a2ec0323", + "reference": "0f7c58cf81dbb5dd67d423a89d577524a2ec0323", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", "symfony/service-contracts": "^1.0|^2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3591,24 +4126,25 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2020-03-27T16:56:45+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { "name": "symfony/yaml", - "version": "v5.0.8", + "version": "v5.1.2", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "482fb4e710e5af3e0e78015f19aa716ad953392f" + "reference": "ea342353a3ef4f453809acc4ebc55382231d4d23" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/482fb4e710e5af3e0e78015f19aa716ad953392f", - "reference": "482fb4e710e5af3e0e78015f19aa716ad953392f", + "url": "https://api.github.com/repos/symfony/yaml/zipball/ea342353a3ef4f453809acc4ebc55382231d4d23", + "reference": "ea342353a3ef4f453809acc4ebc55382231d4d23", "shasum": "" }, "require": { - "php": "^7.2.5", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -3620,10 +4156,13 @@ "suggest": { "symfony/console": "For validating YAML files using the lint command" }, + "bin": [ + "Resources/bin/yaml-lint" + ], "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-master": "5.1-dev" } }, "autoload": { @@ -3650,7 +4189,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2020-04-28T17:58:55+00:00" + "time": "2020-05-20T17:43:50+00:00" }, { "name": "theseer/tokenizer", @@ -3694,22 +4233,22 @@ }, { "name": "vimeo/psalm", - "version": "3.11.5", + "version": "3.11.6", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "3c60609c218d4d4b3b257728b8089094e5c6c6c2" + "reference": "7fc1f50f54bd6b174b1c43a37c1b0b151915d55c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/3c60609c218d4d4b3b257728b8089094e5c6c6c2", - "reference": "3c60609c218d4d4b3b257728b8089094e5c6c6c2", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/7fc1f50f54bd6b174b1c43a37c1b0b151915d55c", + "reference": "7fc1f50f54bd6b174b1c43a37c1b0b151915d55c", "shasum": "" }, "require": { "amphp/amp": "^2.1", "amphp/byte-stream": "^1.5", - "composer/semver": "^1.4", + "composer/semver": "^1.4 || ^2.0 || ^3.0", "composer/xdebug-handler": "^1.1", "ext-dom": "*", "ext-json": "*", @@ -3787,20 +4326,20 @@ "inspection", "php" ], - "time": "2020-05-27T15:12:09+00:00" + "time": "2020-06-17T20:40:35+00:00" }, { "name": "webmozart/assert", - "version": "1.7.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" + "reference": "9dc4f203e36f2b486149058bade43c851dd97451" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", - "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", + "url": "https://api.github.com/repos/webmozart/assert/zipball/9dc4f203e36f2b486149058bade43c851dd97451", + "reference": "9dc4f203e36f2b486149058bade43c851dd97451", "shasum": "" }, "require": { @@ -3808,7 +4347,8 @@ "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "vimeo/psalm": "<3.6.0" + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" }, "require-dev": { "phpunit/phpunit": "^4.8.36 || ^7.5.13" @@ -3835,7 +4375,7 @@ "check", "validate" ], - "time": "2020-02-14T12:15:55+00:00" + "time": "2020-06-16T10:16:42+00:00" }, { "name": "webmozart/glob", @@ -3941,7 +4481,8 @@ "platform": { "php": "^7.4", "ext-ffi": "*", - "ext-pcntl": "*" + "ext-pcntl": "*", + "ext-filter": "*" }, "platform-dev": { "ext-posix": "*" diff --git a/src/Command/Inspector/DaemonCommand.php b/src/Command/Inspector/DaemonCommand.php new file mode 100644 index 00000000..f3e7d6f4 --- /dev/null +++ b/src/Command/Inspector/DaemonCommand.php @@ -0,0 +1,185 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Command\Inspector; + +use Amp\Loop; +use Amp\Promise; +use PhpProfiler\Inspector\Daemon\Dispatcher\DispatchTable; +use PhpProfiler\Inspector\Daemon\Dispatcher\Message\TraceMessage; +use PhpProfiler\Inspector\Daemon\Dispatcher\WorkerPool; +use PhpProfiler\Inspector\Daemon\Reader\Context\PhpReaderContextCreator; +use PhpProfiler\Inspector\Daemon\Searcher\Context\PhpSearcherContextCreator; +use PhpProfiler\Inspector\Settings\DaemonSettings; +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; + +final class DaemonCommand extends Command +{ + private PhpSearcherContextCreator $php_searcher_context_creator; + private PhpReaderContextCreator $php_reader_context_creator; + + public function __construct( + PhpSearcherContextCreator $php_searcher_context_creator, + PhpReaderContextCreator $php_reader_context_creator + ) { + parent::__construct(); + $this->php_reader_context_creator = $php_reader_context_creator; + $this->php_searcher_context_creator = $php_searcher_context_creator; + } + + public function configure(): void + { + $this->setName('inspector:daemon') + ->setDescription('periodically get running function name from an outer process or thread') + ->addOption( + 'target-regex', + 'P', + InputOption::VALUE_OPTIONAL, + 'regex to find the php binary loaded in the target process' + ) + ->addOption('depth', 'd', InputOption::VALUE_OPTIONAL, 'max depth') + ->addOption( + 'sleep-ns', + 's', + InputOption::VALUE_OPTIONAL, + 'nanoseconds between traces (default: 1000 * 1000 * 10)' + ) + ->addOption( + 'max-retries', + 'r', + InputOption::VALUE_OPTIONAL, + 'max retries on contiguous errors of read (default: 10)' + ) + ->addOption( + 'threads', + 'T', + InputOption::VALUE_OPTIONAL, + 'number of workers (default: 8)' + ) + ->addOption( + 'php-regex', + null, + InputOption::VALUE_OPTIONAL, + 'regex to find the php binary loaded in the target process' + ) + ->addOption( + 'libpthread-regex', + null, + InputOption::VALUE_OPTIONAL, + 'regex to find the libpthread.so loaded in the target process' + ) + ->addOption( + 'php-version', + null, + InputOption::VALUE_OPTIONAL, + 'php version of the target' + ) + ->addOption( + 'php-path', + null, + InputOption::VALUE_OPTIONAL, + 'path to the php binary (only needed in tracing chrooted ZTS target)' + ) + ->addOption( + 'libpthread-path', + null, + InputOption::VALUE_OPTIONAL, + 'path to the libpthread.so (only needed in tracing chrooted ZTS target)' + ) + ; + } + + /** + * @param InputInterface $input + * @param OutputInterface $output + * @return int + */ + public function execute(InputInterface $input, OutputInterface $output): int + { + $target_php_settings = TargetPhpSettings::fromConsoleInput($input); + $loop_settings = TraceLoopSettings::fromConsoleInput($input); + $get_trace_settings = GetTraceSettings::fromConsoleInput($input); + $daemon_settings = DaemonSettings::fromConsoleInput($input); + + $searcher_context = $this->php_searcher_context_creator->create(); + Promise\wait($searcher_context->start()); + Promise\wait($searcher_context->sendTargetRegex($daemon_settings->target_regex)); + + $worker_pool = WorkerPool::create( + $this->php_reader_context_creator, + $daemon_settings->threads, + $target_php_settings, + $loop_settings, + $get_trace_settings + ); + + $dispatch_table = new DispatchTable( + $worker_pool, + $target_php_settings, + $loop_settings, + $get_trace_settings + ); + + exec('stty -icanon -echo'); + + Loop::run(function () use ($dispatch_table, $searcher_context, $worker_pool, $output) { + Loop::onReadable( + STDIN, + /** @param resource $stream */ + function (string $watcher_id, $stream) { + $key = fread($stream, 1); + if ($key === 'q') { + Loop::cancel($watcher_id); + Loop::stop(); + } + } + ); + Loop::repeat(10, function () use ($dispatch_table, $searcher_context, $worker_pool, $output) { + $promises = []; + static $searcher_on_read = false; + if (!$searcher_on_read) { + $promises[] = \Amp\call(function () use ($searcher_context, $dispatch_table, &$searcher_on_read) { + $searcher_on_read = true; + $update_target_message = yield $searcher_context->receivePidList(); + $dispatch_table->updateTargets($update_target_message->target_process_list); + $searcher_on_read = false; + }); + } + $readers = $worker_pool->getReadableWorkers(); + foreach ($readers as $pid => $reader) { + $promises[] = \Amp\call( + function () use ($reader, $pid, $worker_pool, $dispatch_table, $output) { + $worker_pool->setOnRead($pid); + $result = yield $reader->receiveTrace(); + $worker_pool->releaseOnRead($pid); + if ($result instanceof TraceMessage) { + $output->write($result->trace); + } else { + $dispatch_table->releaseOne($result->pid); + } + } + ); + } + yield $promises; + }); + }); + + return 0; + } +} diff --git a/src/Command/Inspector/GetCurrentFunctionNameCommand.php b/src/Command/Inspector/GetCurrentFunctionNameCommand.php index 6e743200..bae335b6 100644 --- a/src/Command/Inspector/GetCurrentFunctionNameCommand.php +++ b/src/Command/Inspector/GetCurrentFunctionNameCommand.php @@ -13,9 +13,11 @@ namespace PhpProfiler\Command\Inspector; -use PhpProfiler\Command\CommandSettingsException; -use PhpProfiler\Command\Inspector\Settings\LoopSettings; -use PhpProfiler\Command\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\Settings\InspectorSettingsException; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; +use PhpProfiler\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\TraceLoopProvider; use PhpProfiler\Lib\Elf\Parser\ElfParserException; use PhpProfiler\Lib\Elf\Process\ProcessSymbolReaderException; use PhpProfiler\Lib\Elf\Tls\TlsFinderException; @@ -109,22 +111,22 @@ public function configure(): void * @throws ProcessSymbolReaderException * @throws ElfParserException * @throws TlsFinderException - * @throws CommandSettingsException + * @throws InspectorSettingsException */ public function execute(InputInterface $input, OutputInterface $output): int { $target_process_settings = TargetProcessSettings::fromConsoleInput($input); - $loop_settings = LoopSettings::fromConsoleInput($input); + $target_php_settings = TargetPhpSettings::fromConsoleInput($input); + $loop_settings = TraceLoopSettings::fromConsoleInput($input); - $pid = $target_process_settings->pid; - $eg_address = $this->php_globals_finder->findExecutorGlobals($target_process_settings); + $eg_address = $this->php_globals_finder->findExecutorGlobals($target_process_settings, $target_php_settings); $this->loop_provider->getMainLoop( - function () use ($target_process_settings, $eg_address, $output): bool { + function () use ($target_process_settings, $target_php_settings, $eg_address, $output): bool { $output->writeln( $this->executor_globals_reader->readCurrentFunctionName( $target_process_settings->pid, - $target_process_settings->php_version, + $target_php_settings->php_version, $eg_address ) ); diff --git a/src/Command/Inspector/GetEgAddressCommand.php b/src/Command/Inspector/GetEgAddressCommand.php index c66d1323..7b52e944 100644 --- a/src/Command/Inspector/GetEgAddressCommand.php +++ b/src/Command/Inspector/GetEgAddressCommand.php @@ -13,8 +13,9 @@ namespace PhpProfiler\Command\Inspector; -use PhpProfiler\Command\CommandSettingsException; -use PhpProfiler\Command\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\Settings\InspectorSettingsException; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TargetProcessSettings; use PhpProfiler\Lib\Elf\Parser\ElfParserException; use PhpProfiler\Lib\Elf\Tls\TlsFinderException; use PhpProfiler\Lib\PhpProcessReader\PhpGlobalsFinder; @@ -91,14 +92,18 @@ public function configure(): void * @throws ProcessSymbolReaderException * @throws ElfParserException * @throws TlsFinderException - * @throws CommandSettingsException + * @throws InspectorSettingsException */ public function execute(InputInterface $input, OutputInterface $output): int { $target_process_settings = TargetProcessSettings::fromConsoleInput($input); + $target_php_settings = TargetPhpSettings::fromConsoleInput($input); $output->writeln( - '0x' . dechex($this->php_globals_finder->findExecutorGlobals($target_process_settings)) + sprintf( + '0x%s', + dechex($this->php_globals_finder->findExecutorGlobals($target_process_settings, $target_php_settings)) + ) ); return 0; diff --git a/src/Command/Inspector/GetTraceCommand.php b/src/Command/Inspector/GetTraceCommand.php index 29dd56f7..300d0ee3 100644 --- a/src/Command/Inspector/GetTraceCommand.php +++ b/src/Command/Inspector/GetTraceCommand.php @@ -13,10 +13,12 @@ namespace PhpProfiler\Command\Inspector; -use PhpProfiler\Command\CommandSettingsException; -use PhpProfiler\Command\Inspector\Settings\GetTraceSettings; -use PhpProfiler\Command\Inspector\Settings\LoopSettings; -use PhpProfiler\Command\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\Settings\InspectorSettingsException; +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; +use PhpProfiler\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\TraceLoopProvider; use PhpProfiler\Lib\Elf\Parser\ElfParserException; use PhpProfiler\Lib\Elf\Process\ProcessSymbolReaderException; use PhpProfiler\Lib\Elf\Tls\TlsFinderException; @@ -111,21 +113,28 @@ public function configure(): void * @throws ProcessSymbolReaderException * @throws ElfParserException * @throws TlsFinderException - * @throws CommandSettingsException + * @throws InspectorSettingsException */ public function execute(InputInterface $input, OutputInterface $output): int { $target_process_settings = TargetProcessSettings::fromConsoleInput($input); - $loop_settings = LoopSettings::fromConsoleInput($input); + $target_php_settings = TargetPhpSettings::fromConsoleInput($input); + $loop_settings = TraceLoopSettings::fromConsoleInput($input); $get_trace_settings = GetTraceSettings::fromConsoleInput($input); - $eg_address = $this->php_globals_finder->findExecutorGlobals($target_process_settings); + $eg_address = $this->php_globals_finder->findExecutorGlobals($target_process_settings, $target_php_settings); $this->loop_provider->getMainLoop( - function () use ($get_trace_settings, $target_process_settings, $eg_address, $output): bool { + function () use ( + $get_trace_settings, + $target_process_settings, + $target_php_settings, + $eg_address, + $output + ): bool { $call_trace = $this->executor_globals_reader->readCallTrace( $target_process_settings->pid, - $target_process_settings->php_version, + $target_php_settings->php_version, $eg_address, $get_trace_settings->depth ); diff --git a/src/Command/Inspector/TraceLoopProvider.php b/src/Command/Inspector/TraceLoopProvider.php deleted file mode 100644 index c6476b69..00000000 --- a/src/Command/Inspector/TraceLoopProvider.php +++ /dev/null @@ -1,42 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -declare(strict_types=1); - -namespace PhpProfiler\Command\Inspector; - -use PhpProfiler\Command\Inspector\Settings\LoopSettings; -use PhpProfiler\Lib\Loop\Loop; -use PhpProfiler\Lib\Loop\LoopBuilder; -use PhpProfiler\Lib\Loop\LoopProcess\CallableLoop; -use PhpProfiler\Lib\Loop\LoopProcess\KeyboardCancelLoop; -use PhpProfiler\Lib\Loop\LoopProcess\NanoSleepLoop; -use PhpProfiler\Lib\Loop\LoopProcess\RetryOnExceptionLoop; -use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderException; - -final class TraceLoopProvider -{ - private LoopBuilder $loop_builder; - - public function __construct(LoopBuilder $loop_builder) - { - $this->loop_builder = $loop_builder; - } - - public function getMainLoop(callable $main, LoopSettings $settings): Loop - { - return $this->loop_builder->addProcess(NanoSleepLoop::class, [$settings->sleep_nano_seconds]) - ->addProcess(RetryOnExceptionLoop::class, [$settings->max_retries, [MemoryReaderException::class]]) - ->addProcess(KeyboardCancelLoop::class, [$settings->cancel_key]) - ->addProcess(CallableLoop::class, [$main]) - ->build(); - } -} diff --git a/src/Inspector/Daemon/Dispatcher/DispatchTable.php b/src/Inspector/Daemon/Dispatcher/DispatchTable.php new file mode 100644 index 00000000..17ec422a --- /dev/null +++ b/src/Inspector/Daemon/Dispatcher/DispatchTable.php @@ -0,0 +1,75 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Dispatcher; + +use PhpProfiler\Inspector\Daemon\Reader\Context\PhpReaderContext; +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; + +final class DispatchTable +{ + private WorkerPool $worker_pool; + private TargetProcessList $assigned; + private TargetPhpSettings $target_php_settings; + private TraceLoopSettings $trace_loop_settings; + private GetTraceSettings $get_trace_settings; + /** @var array */ + private array $dispatch_table = []; + + public function __construct( + WorkerPool $worker_pool, + TargetPhpSettings $target_php_settings, + TraceLoopSettings $trace_loop_settings, + GetTraceSettings $get_trace_settings + ) { + $this->worker_pool = $worker_pool; + $this->target_php_settings = $target_php_settings; + $this->trace_loop_settings = $trace_loop_settings; + $this->get_trace_settings = $get_trace_settings; + $this->assigned = new TargetProcessList(); + } + + public function updateTargets(TargetProcessList $update): void + { + $diff = $this->assigned->getDiff($update); + $this->release($diff); + $this->assigned = $this->assigned->getDiff($diff); + $unassigned_new = $update->getDiff($this->assigned); + for ($worker = $this->worker_pool->getFreeWorker(); $worker; $worker = $this->worker_pool->getFreeWorker()) { + $picked = $unassigned_new->pickOne(); + if (is_null($picked)) { + break; + } + $this->assigned->putOne($picked); + $this->dispatch_table[$picked] = $worker; + $worker->sendAttach($picked); + } + } + + public function release(TargetProcessList $targets): void + { + foreach ($targets->getArray() as $pid) { + $this->releaseOne($pid); + } + } + + public function releaseOne(int $pid): void + { + $worker = $this->dispatch_table[$pid]; + $this->worker_pool->returnWorkerToPool($worker); + unset($this->dispatch_table[$pid]); + $this->assigned = $this->assigned->getDiff(new TargetProcessList($pid)); + } +} diff --git a/src/Inspector/Daemon/Dispatcher/Message/DetachWorkerMessage.php b/src/Inspector/Daemon/Dispatcher/Message/DetachWorkerMessage.php new file mode 100644 index 00000000..41005594 --- /dev/null +++ b/src/Inspector/Daemon/Dispatcher/Message/DetachWorkerMessage.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Dispatcher\Message; + +final class DetachWorkerMessage +{ + public int $pid; + + public function __construct(int $pid) + { + $this->pid = $pid; + } +} diff --git a/src/Inspector/Daemon/Dispatcher/Message/TraceMessage.php b/src/Inspector/Daemon/Dispatcher/Message/TraceMessage.php new file mode 100644 index 00000000..5663771b --- /dev/null +++ b/src/Inspector/Daemon/Dispatcher/Message/TraceMessage.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Dispatcher\Message; + +final class TraceMessage +{ + public string $trace; + + public function __construct(string $trace) + { + $this->trace = $trace; + } +} diff --git a/src/Inspector/Daemon/Dispatcher/Message/UpdateTargetProcessMessage.php b/src/Inspector/Daemon/Dispatcher/Message/UpdateTargetProcessMessage.php new file mode 100644 index 00000000..9ac9d53b --- /dev/null +++ b/src/Inspector/Daemon/Dispatcher/Message/UpdateTargetProcessMessage.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Dispatcher\Message; + +use PhpProfiler\Inspector\Daemon\Dispatcher\TargetProcessList; + +final class UpdateTargetProcessMessage +{ + public TargetProcessList $target_process_list; + + public function __construct(TargetProcessList $target_process_list) + { + $this->target_process_list = $target_process_list; + } +} diff --git a/src/Inspector/Daemon/Dispatcher/TargetProcessList.php b/src/Inspector/Daemon/Dispatcher/TargetProcessList.php new file mode 100644 index 00000000..8e3f4ae4 --- /dev/null +++ b/src/Inspector/Daemon/Dispatcher/TargetProcessList.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Dispatcher; + +final class TargetProcessList +{ + /** @var int[] */ + private array $pid_list; + + public function __construct(int ...$pid_list) + { + $this->pid_list = $pid_list; + } + + public function pickOne(): ?int + { + return array_pop($this->pid_list); + } + + public function putOne(int $pid): void + { + $this->pid_list[] = $pid; + } + + public function getDiff(TargetProcessList $compare_list): self + { + return new self(...array_diff($this->pid_list, $compare_list->pid_list)); + } + + /** + * @return int[] + */ + public function getArray(): array + { + return $this->pid_list; + } +} diff --git a/src/Inspector/Daemon/Dispatcher/WorkerPool.php b/src/Inspector/Daemon/Dispatcher/WorkerPool.php new file mode 100644 index 00000000..765c2cc8 --- /dev/null +++ b/src/Inspector/Daemon/Dispatcher/WorkerPool.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Dispatcher; + +use PhpProfiler\Inspector\Daemon\Reader\Context\PhpReaderContext; +use PhpProfiler\Inspector\Daemon\Reader\Context\PhpReaderContextCreator; +use Amp\Promise; +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; + +final class WorkerPool +{ + /** @var array */ + private array $contexts; + + /** @var array */ + private array $is_free_list; + + /** @var array */ + private array $on_read_list; + + public function __construct(PhpReaderContext ...$contexts) + { + $this->contexts = $contexts; + $this->is_free_list = array_fill(0, count($contexts), true); + $this->on_read_list = array_fill(0, count($contexts), false); + } + + public static function create( + PhpReaderContextCreator $creator, + int $number, + TargetPhpSettings $target_php_settings, + TraceLoopSettings $loop_settings, + GetTraceSettings $get_trace_settings + ): self { + $contexts = []; + $started = []; + for ($i = 0; $i < $number; $i++) { + $context = $creator->create(); + $started[] = $context->start(); + $contexts[] = $context; + } + Promise\wait(Promise\all($started)); + $send_settings = []; + for ($i = 0; $i < $number; $i++) { + $send_settings[] = $contexts[$i]->sendSettings( + $target_php_settings, + $loop_settings, + $get_trace_settings + ); + } + Promise\wait(Promise\all($send_settings)); + + return new self(...$contexts); + } + + public function getFreeWorker(): ?PhpReaderContext + { + foreach ($this->contexts as $key => $context) { + if ($this->is_free_list[$key]) { + $this->is_free_list[$key] = false; + return $context; + } + } + return null; + } + + /** + * @return iterable + */ + public function getReadableWorkers(): iterable + { + foreach ($this->contexts as $key => $context) { + if (!$this->is_free_list[$key] and !$this->on_read_list[$key]) { + yield $key => $context; + } + } + } + + public function returnWorkerToPool(PhpReaderContext $context_to_return): void + { + foreach ($this->contexts as $key => $context) { + if ($context === $context_to_return) { + $this->is_free_list[$key] = true; + } + } + } + + public function setOnRead(int $pid): void + { + $this->on_read_list[$pid] = true; + } + + public function releaseOnRead(int $pid): void + { + $this->on_read_list[$pid] = false; + } +} diff --git a/src/Inspector/Daemon/Reader/Context/PhpReaderContext.php b/src/Inspector/Daemon/Reader/Context/PhpReaderContext.php new file mode 100644 index 00000000..688536c6 --- /dev/null +++ b/src/Inspector/Daemon/Reader/Context/PhpReaderContext.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Reader\Context; + +use Amp\Parallel\Context; +use Amp\Promise; +use PhpProfiler\Inspector\Daemon\Dispatcher\Message\DetachWorkerMessage; +use PhpProfiler\Inspector\Daemon\Dispatcher\Message\TraceMessage; +use PhpProfiler\Inspector\Daemon\Reader\Message\AttachMessage; +use PhpProfiler\Inspector\Daemon\Reader\Message\SetSettingsMessage; +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; + +final class PhpReaderContext +{ + private Context\Context $context; + + public function __construct(Context\Context $context) + { + $this->context = $context; + } + + public function start(): Promise + { + return $this->context->start(); + } + + /** + * @param TargetPhpSettings $target_php_settings + * @param TraceLoopSettings $loop_settings + * @param GetTraceSettings $get_trace_settings + * @return Promise + */ + public function sendSettings( + TargetPhpSettings $target_php_settings, + TraceLoopSettings $loop_settings, + GetTraceSettings $get_trace_settings + ): Promise { + /** @var Promise */ + return $this->context->send( + new SetSettingsMessage( + $target_php_settings, + $loop_settings, + $get_trace_settings + ) + ); + } + + /** + * @param int $pid + * @return Promise + */ + public function sendAttach(int $pid): Promise + { + /** @var Promise */ + return $this->context->send( + new AttachMessage($pid) + ); + } + + public function isRunning(): bool + { + return $this->context->isRunning(); + } + + /** + * @return Promise + * @psalm-yield Promise + */ + public function receiveTrace(): Promise + { + /** @var Promise */ + return $this->context->receive(); + } +} diff --git a/src/Inspector/Daemon/Reader/Context/PhpReaderContextCreator.php b/src/Inspector/Daemon/Reader/Context/PhpReaderContextCreator.php new file mode 100644 index 00000000..eb27dca8 --- /dev/null +++ b/src/Inspector/Daemon/Reader/Context/PhpReaderContextCreator.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Reader\Context; + +use Amp\Parallel\Context; + +final class PhpReaderContextCreator +{ + public function create(): PhpReaderContext + { + return new PhpReaderContext(Context\create(__DIR__ . '/php-reader.php')); + } +} diff --git a/src/Inspector/Daemon/Reader/Context/php-reader.php b/src/Inspector/Daemon/Reader/Context/php-reader.php new file mode 100644 index 00000000..c87a0180 --- /dev/null +++ b/src/Inspector/Daemon/Reader/Context/php-reader.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +use Amp\Parallel\Sync\Channel; +use DI\ContainerBuilder; +use PhpProfiler\Inspector\Daemon\Dispatcher\Message\DetachWorkerMessage; +use PhpProfiler\Inspector\Daemon\Reader\Message\AttachMessage; +use PhpProfiler\Inspector\Daemon\Reader\Message\SetSettingsMessage; +use PhpProfiler\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\Daemon\Reader\PhpReaderTask; + +return function (Channel $channel): \Generator { + + $container = (new ContainerBuilder())->addDefinitions(__DIR__ . '/../../../../../config/di.php')->build(); + + /** @var PhpReaderTask $reader */ + $reader = $container->make(PhpReaderTask::class, ['channel' => $channel]); + + /** @var SetSettingsMessage $set_settings_message */ + $set_settings_message = yield $channel->receive(); + + while (1) { + /** @var AttachMessage $attach_message */ + $attach_message = yield $channel->receive(); + + $target_process_settings = new TargetProcessSettings( + $attach_message->pid + ); + + yield from $reader->run( + $target_process_settings, + $set_settings_message->trace_loop_settings, + $set_settings_message->target_php_settings, + $set_settings_message->get_trace_settings + ); + + yield $channel->send( + new DetachWorkerMessage($attach_message->pid) + ); + } +}; diff --git a/src/Inspector/Daemon/Reader/Message/AttachMessage.php b/src/Inspector/Daemon/Reader/Message/AttachMessage.php new file mode 100644 index 00000000..9fc37501 --- /dev/null +++ b/src/Inspector/Daemon/Reader/Message/AttachMessage.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Reader\Message; + +final class AttachMessage +{ + public int $pid; + + public function __construct(int $pid) + { + $this->pid = $pid; + } +} diff --git a/src/Inspector/Daemon/Reader/Message/SetSettingsMessage.php b/src/Inspector/Daemon/Reader/Message/SetSettingsMessage.php new file mode 100644 index 00000000..92916b75 --- /dev/null +++ b/src/Inspector/Daemon/Reader/Message/SetSettingsMessage.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Reader\Message; + +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; + +final class SetSettingsMessage +{ + public TargetPhpSettings $target_php_settings; + public TraceLoopSettings $trace_loop_settings; + public GetTraceSettings $get_trace_settings; + + public function __construct( + TargetPhpSettings $target_php_settings, + TraceLoopSettings $trace_loop_settings, + GetTraceSettings $get_trace_settings + ) { + $this->target_php_settings = $target_php_settings; + $this->trace_loop_settings = $trace_loop_settings; + $this->get_trace_settings = $get_trace_settings; + } +} diff --git a/src/Inspector/Daemon/Reader/PhpReaderTask.php b/src/Inspector/Daemon/Reader/PhpReaderTask.php new file mode 100644 index 00000000..b86c5169 --- /dev/null +++ b/src/Inspector/Daemon/Reader/PhpReaderTask.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Reader; + +use Amp\Parallel\Sync\Channel; +use Generator; +use PhpProfiler\Inspector\Daemon\Dispatcher\Message\TraceMessage; +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; +use PhpProfiler\Lib\PhpProcessReader\PhpGlobalsFinder; +use PhpProfiler\Lib\PhpProcessReader\PhpMemoryReader\ExecutorGlobalsReader; + +final class PhpReaderTask +{ + private Channel $channel; + private PhpGlobalsFinder $php_globals_finder; + private ExecutorGlobalsReader $executor_globals_reader; + private ReaderLoopProvider $reader_loop_provider; + + public function __construct( + Channel $channel, + PhpGlobalsFinder $php_globals_finder, + ExecutorGlobalsReader $executor_globals_reader, + ReaderLoopProvider $reader_loop_provider + ) { + $this->channel = $channel; + $this->php_globals_finder = $php_globals_finder; + $this->executor_globals_reader = $executor_globals_reader; + $this->reader_loop_provider = $reader_loop_provider; + } + + public function run( + TargetProcessSettings $target_process_settings, + TraceLoopSettings $loop_settings, + TargetPhpSettings $target_php_settings, + GetTraceSettings $get_trace_settings + ): Generator { + $eg_address = $this->php_globals_finder->findExecutorGlobals($target_process_settings, $target_php_settings); + + $loop = $this->reader_loop_provider->getMainLoop( + function () use ( + $get_trace_settings, + $target_process_settings, + $target_php_settings, + $eg_address + ): \Generator { + $call_trace = $this->executor_globals_reader->readCallTrace( + $target_process_settings->pid, + $target_php_settings->php_version, + $eg_address, + $get_trace_settings->depth + ); + yield $this->channel->send( + new TraceMessage(join(PHP_EOL, $call_trace) . PHP_EOL) + ); + }, + $loop_settings + ); + yield from $loop->invoke(); + } +} diff --git a/src/Inspector/Daemon/Reader/ReaderLoopProvider.php b/src/Inspector/Daemon/Reader/ReaderLoopProvider.php new file mode 100644 index 00000000..e0e641b1 --- /dev/null +++ b/src/Inspector/Daemon/Reader/ReaderLoopProvider.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Reader; + +use PhpProfiler\Inspector\Settings\TraceLoopSettings; +use PhpProfiler\Lib\Loop\AsyncLoop; +use PhpProfiler\Lib\Loop\AsyncLoopBuilder; +use PhpProfiler\Lib\Loop\AsyncLoopMiddleware\CallableMiddlewareAsync; +use PhpProfiler\Lib\Loop\AsyncLoopMiddleware\NanoSleepMiddlewareAsync; +use PhpProfiler\Lib\Loop\AsyncLoopMiddleware\RetryOnExceptionMiddlewareAsync; +use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderException; + +final class ReaderLoopProvider +{ + private AsyncLoopBuilder $loop_builder; + + public function __construct(AsyncLoopBuilder $loop_builder) + { + $this->loop_builder = $loop_builder; + } + + public function getMainLoop(callable $main, TraceLoopSettings $settings): AsyncLoop + { + return $this->loop_builder + ->addProcess( + RetryOnExceptionMiddlewareAsync::class, + [ + $settings->max_retries, + [MemoryReaderException::class] + ] + ) + ->addProcess(NanoSleepMiddlewareAsync::class, [$settings->sleep_nano_seconds]) + ->addProcess(CallableMiddlewareAsync::class, [$main]) + ->build(); + } +} diff --git a/src/Inspector/Daemon/Searcher/Context/PhpSearcherContext.php b/src/Inspector/Daemon/Searcher/Context/PhpSearcherContext.php new file mode 100644 index 00000000..8b14c085 --- /dev/null +++ b/src/Inspector/Daemon/Searcher/Context/PhpSearcherContext.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Searcher\Context; + +use Amp\Parallel\Context; +use Amp\Promise; +use PhpProfiler\Inspector\Daemon\Dispatcher\Message\UpdateTargetProcessMessage; + +final class PhpSearcherContext +{ + private Context\Context $context; + + public function __construct(Context\Context $context) + { + $this->context = $context; + } + + /** + * @return Promise + */ + public function start(): Promise + { + return $this->context->start(); + } + + /** + * @param string $regex + * @return Promise + */ + public function sendTargetRegex(string $regex): Promise + { + /** @var Promise */ + return $this->context->send($regex); + } + + /** + * @return Promise + * @psalm-yield Promise + */ + public function receivePidList(): Promise + { + /** @var Promise */ + return $this->context->receive(); + } +} diff --git a/src/Inspector/Daemon/Searcher/Context/PhpSearcherContextCreator.php b/src/Inspector/Daemon/Searcher/Context/PhpSearcherContextCreator.php new file mode 100644 index 00000000..d59642d8 --- /dev/null +++ b/src/Inspector/Daemon/Searcher/Context/PhpSearcherContextCreator.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Searcher\Context; + +use Amp\Parallel\Context; + +final class PhpSearcherContextCreator +{ + public function create(): PhpSearcherContext + { + return new PhpSearcherContext(Context\create(__DIR__ . '/php-searcher.php')); + } +} diff --git a/src/Inspector/Daemon/Searcher/Context/php-searcher.php b/src/Inspector/Daemon/Searcher/Context/php-searcher.php new file mode 100644 index 00000000..97b9ea3a --- /dev/null +++ b/src/Inspector/Daemon/Searcher/Context/php-searcher.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +use Amp\Parallel\Sync\Channel; +use DI\ContainerBuilder; +use PhpProfiler\Inspector\Daemon\Searcher\PhpSearcherTask; + +return function (Channel $channel): \Generator { + $container = (new ContainerBuilder())->addDefinitions(__DIR__ . '/../../../../../config/di.php')->build(); + + /** @var PhpSearcherTask $searcher */ + $searcher = $container->make(PhpSearcherTask::class, ['channel' => $channel]); + + /** @var string $target_regex */ + $target_regex = yield $channel->receive(); + + while (1) { + yield from $searcher->run($target_regex); + } +}; diff --git a/src/Inspector/Daemon/Searcher/PhpSearcherTask.php b/src/Inspector/Daemon/Searcher/PhpSearcherTask.php new file mode 100644 index 00000000..d6dac039 --- /dev/null +++ b/src/Inspector/Daemon/Searcher/PhpSearcherTask.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Searcher; + +use Amp\Parallel\Sync\Channel; +use Generator; +use PhpProfiler\Inspector\Daemon\Dispatcher\Message\UpdateTargetProcessMessage; +use PhpProfiler\Inspector\Daemon\Dispatcher\TargetProcessList; +use PhpProfiler\Lib\Process\Search\ProcessSearcher; + +final class PhpSearcherTask +{ + private Channel $channel; + private ProcessSearcher $process_searcher; + + public function __construct(Channel $channel, ProcessSearcher $process_searcher) + { + $this->channel = $channel; + $this->process_searcher = $process_searcher; + } + + public function run(string $target_regex): Generator + { + while (1) { + yield $this->channel->send( + new UpdateTargetProcessMessage( + new TargetProcessList( + ...$this->process_searcher->searchByRegex($target_regex) + ) + ) + ); + sleep(1); + } + } +} diff --git a/src/Inspector/Settings/DaemonInspectorSettingsException.php b/src/Inspector/Settings/DaemonInspectorSettingsException.php new file mode 100644 index 00000000..91026674 --- /dev/null +++ b/src/Inspector/Settings/DaemonInspectorSettingsException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Settings; + +final class DaemonInspectorSettingsException extends InspectorSettingsException +{ + public const THREADS_IS_NOT_INTEGER = 1; + public const TARGET_REGEX_IS_NOT_STRING = 2; + + protected const ERRORS = [ + self::THREADS_IS_NOT_INTEGER => 'threads is not integer', + self::TARGET_REGEX_IS_NOT_STRING => 'target-regex is not string', + ]; +} diff --git a/src/Inspector/Settings/DaemonSettings.php b/src/Inspector/Settings/DaemonSettings.php new file mode 100644 index 00000000..c3ca1217 --- /dev/null +++ b/src/Inspector/Settings/DaemonSettings.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Settings; + +use PhpProfiler\Inspector\Settings\InspectorSettingsException; +use Symfony\Component\Console\Input\InputInterface; + +final class DaemonSettings +{ + private const TARGET_REGEX_DEFAULT = '^php-fpm'; + + public string $target_regex; + public int $threads; + + /** + * DaemonSettings constructor. + * @param string $target_regex + * @param int $threads + */ + public function __construct(string $target_regex, int $threads) + { + $this->target_regex = $target_regex; + $this->threads = $threads; + } + + /** + * @param InputInterface $input + * @return self + * @throws InspectorSettingsException + */ + public static function fromConsoleInput(InputInterface $input): self + { + $threads = $input->getOption('threads'); + if (is_null($threads)) { + $threads = 8; + } + $threads = filter_var($threads, FILTER_VALIDATE_INT); + if ($threads === false) { + throw DaemonInspectorSettingsException::create(DaemonInspectorSettingsException::THREADS_IS_NOT_INTEGER); + } + + $target_regex = $input->getOption('target-regex') ?? self::TARGET_REGEX_DEFAULT; + if (!is_string($target_regex)) { + throw DaemonInspectorSettingsException::create( + DaemonInspectorSettingsException::TARGET_REGEX_IS_NOT_STRING + ); + } + + return new self('{' . $target_regex . '}', $threads); + } +} diff --git a/src/Command/Inspector/Settings/GetTraceSettingsException.php b/src/Inspector/Settings/GetTraceInspectorSettingsException.php similarity index 70% rename from src/Command/Inspector/Settings/GetTraceSettingsException.php rename to src/Inspector/Settings/GetTraceInspectorSettingsException.php index 40b6156d..677bcf9c 100644 --- a/src/Command/Inspector/Settings/GetTraceSettingsException.php +++ b/src/Inspector/Settings/GetTraceInspectorSettingsException.php @@ -11,11 +11,9 @@ declare(strict_types=1); -namespace PhpProfiler\Command\Inspector\Settings; +namespace PhpProfiler\Inspector\Settings; -use PhpProfiler\Command\CommandSettingsException; - -class GetTraceSettingsException extends CommandSettingsException +final class GetTraceInspectorSettingsException extends InspectorSettingsException { public const DEPTH_IS_NOT_INTEGER = 1; diff --git a/src/Command/Inspector/Settings/GetTraceSettings.php b/src/Inspector/Settings/GetTraceSettings.php similarity index 75% rename from src/Command/Inspector/Settings/GetTraceSettings.php rename to src/Inspector/Settings/GetTraceSettings.php index 3af71c5f..77e86dc8 100644 --- a/src/Command/Inspector/Settings/GetTraceSettings.php +++ b/src/Inspector/Settings/GetTraceSettings.php @@ -11,12 +11,12 @@ declare(strict_types=1); -namespace PhpProfiler\Command\Inspector\Settings; +namespace PhpProfiler\Inspector\Settings; -use PhpProfiler\Command\CommandSettingsException; +use PhpProfiler\Inspector\Settings\InspectorSettingsException; use Symfony\Component\Console\Input\InputInterface; -class GetTraceSettings +final class GetTraceSettings { public int $depth; @@ -32,7 +32,7 @@ public function __construct(int $depth) /** * @param InputInterface $input * @return self - * @throws CommandSettingsException + * @throws InspectorSettingsException */ public static function fromConsoleInput(InputInterface $input): self { @@ -42,7 +42,7 @@ public static function fromConsoleInput(InputInterface $input): self } $depth = filter_var($depth, FILTER_VALIDATE_INT); if ($depth === false) { - throw GetTraceSettingsException::create(GetTraceSettingsException::DEPTH_IS_NOT_INTEGER); + throw GetTraceInspectorSettingsException::create(GetTraceInspectorSettingsException::DEPTH_IS_NOT_INTEGER); } return new self($depth); } diff --git a/src/Command/CommandSettingsException.php b/src/Inspector/Settings/InspectorSettingsException.php similarity index 83% rename from src/Command/CommandSettingsException.php rename to src/Inspector/Settings/InspectorSettingsException.php index 0e3864c1..eccd699e 100644 --- a/src/Command/CommandSettingsException.php +++ b/src/Inspector/Settings/InspectorSettingsException.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace PhpProfiler\Command; +namespace PhpProfiler\Inspector\Settings; use LogicException; -abstract class CommandSettingsException extends \Exception +abstract class InspectorSettingsException extends \Exception { public const ERROR_NONE = 0; @@ -37,7 +37,7 @@ public static function getErrors(): array * @param int $error_no * @return static */ - public static function create(int $error_no): CommandSettingsException + public static function create(int $error_no): InspectorSettingsException { if (!isset(static::ERRORS[$error_no])) { throw new LogicException('wrong creation of CommandSettingException'); diff --git a/src/Command/Inspector/Settings/TargetProcessSettingsException.php b/src/Inspector/Settings/TargetPhpInspectorSettingsException.php similarity index 72% rename from src/Command/Inspector/Settings/TargetProcessSettingsException.php rename to src/Inspector/Settings/TargetPhpInspectorSettingsException.php index 5dceef24..8a3ed64e 100644 --- a/src/Command/Inspector/Settings/TargetProcessSettingsException.php +++ b/src/Inspector/Settings/TargetPhpInspectorSettingsException.php @@ -11,14 +11,10 @@ declare(strict_types=1); -namespace PhpProfiler\Command\Inspector\Settings; +namespace PhpProfiler\Inspector\Settings; -use PhpProfiler\Command\CommandSettingsException; - -class TargetProcessSettingsException extends CommandSettingsException +final class TargetPhpInspectorSettingsException extends InspectorSettingsException { - public const PID_NOT_SPECIFIED = 1; - public const PID_IS_NOT_INTEGER = 2; public const PHP_REGEX_IS_NOT_STRING = 3; public const LIBPTHREAD_REGEX_IS_NOT_STRING = 4; public const PHP_PATH_IS_NOT_STRING = 5; @@ -26,8 +22,6 @@ class TargetProcessSettingsException extends CommandSettingsException public const TARGET_PHP_VERSION_INVALID = 7; protected const ERRORS = [ - self::PID_NOT_SPECIFIED => 'pid is not specified', - self::PID_IS_NOT_INTEGER => 'pid is not integer', self::PHP_REGEX_IS_NOT_STRING => 'php-regex must be a string', self::LIBPTHREAD_REGEX_IS_NOT_STRING => 'libpthread-regex must be a string', self::PHP_PATH_IS_NOT_STRING => 'php-path must be a string', diff --git a/src/Command/Inspector/Settings/TargetProcessSettings.php b/src/Inspector/Settings/TargetPhpSettings.php similarity index 63% rename from src/Command/Inspector/Settings/TargetProcessSettings.php rename to src/Inspector/Settings/TargetPhpSettings.php index 4b6f11db..45ee5c6e 100644 --- a/src/Command/Inspector/Settings/TargetProcessSettings.php +++ b/src/Inspector/Settings/TargetPhpSettings.php @@ -11,19 +11,17 @@ declare(strict_types=1); -namespace PhpProfiler\Command\Inspector\Settings; +namespace PhpProfiler\Inspector\Settings; -use PhpProfiler\Command\CommandSettingsException; use PhpProfiler\Lib\PhpInternals\ZendTypeReader; use Symfony\Component\Console\Input\InputInterface; -class TargetProcessSettings +final class TargetPhpSettings { private const PHP_REGEX_DEFAULT = '.*/(php(74|7.4|80|8.0)?|php-fpm|libphp[78].*\.so)$'; private const LIBPTHREAD_REGEX_DEFAULT = '.*/libpthread.*\.so$'; private const TARGET_PHP_VERSION_DEFAULT = ZendTypeReader::V74; - public int $pid; public string $php_regex; public string $libpthread_regex; /** @psalm-var value-of $php_version */ @@ -33,7 +31,6 @@ class TargetProcessSettings /** * GetTraceSettings constructor. - * @param int $pid * @param string $php_regex * @param string $libpthread_regex * @param string $php_version @@ -42,14 +39,12 @@ class TargetProcessSettings * @psalm-param value-of $php_version */ public function __construct( - int $pid, string $php_regex = self::PHP_REGEX_DEFAULT, string $libpthread_regex = self::LIBPTHREAD_REGEX_DEFAULT, string $php_version = ZendTypeReader::V74, ?string $php_path = null, ?string $libpthread_path = null ) { - $this->pid = $pid; $this->php_regex = '{' . $php_regex . '}'; $this->libpthread_regex = '{' . $libpthread_regex . '}'; $this->php_version = $php_version; @@ -57,62 +52,44 @@ public function __construct( $this->libpthread_path = $libpthread_path; } - /** - * @param InputInterface $input - * @return self - * @throws CommandSettingsException - */ public static function fromConsoleInput(InputInterface $input): self { - $pid = $input->getOption('pid'); - if (is_null($pid)) { - throw TargetProcessSettingsException::create( - TargetProcessSettingsException::PID_NOT_SPECIFIED - ); - } - $pid = filter_var($pid, FILTER_VALIDATE_INT); - if ($pid === false) { - throw TargetProcessSettingsException::create( - TargetProcessSettingsException::PID_NOT_SPECIFIED - ); - } - $php_regex = $input->getOption('php-regex') ?? self::PHP_REGEX_DEFAULT; if (!is_string($php_regex)) { - throw TargetProcessSettingsException::create( - TargetProcessSettingsException::PHP_REGEX_IS_NOT_STRING + throw TargetPhpInspectorSettingsException::create( + TargetPhpInspectorSettingsException::PHP_REGEX_IS_NOT_STRING ); } $libpthread_regex = $input->getOption('libpthread-regex') ?? self::LIBPTHREAD_REGEX_DEFAULT; if (!is_string($libpthread_regex)) { - throw TargetProcessSettingsException::create( - TargetProcessSettingsException::LIBPTHREAD_REGEX_IS_NOT_STRING + throw TargetPhpInspectorSettingsException::create( + TargetPhpInspectorSettingsException::LIBPTHREAD_REGEX_IS_NOT_STRING ); } $php_version = $input->getOption('php-version') ?? self::TARGET_PHP_VERSION_DEFAULT; if (!in_array($php_version, ZendTypeReader::ALL_SUPPORTED_VERSIONS, true)) { - throw TargetProcessSettingsException::create( - TargetProcessSettingsException::TARGET_PHP_VERSION_INVALID + throw TargetPhpInspectorSettingsException::create( + TargetPhpInspectorSettingsException::TARGET_PHP_VERSION_INVALID ); } /** @psalm-var value-of $php_version */ $php_path = $input->getOption('php-path'); if (!is_null($php_path) and !is_string($php_path)) { - throw TargetProcessSettingsException::create( - TargetProcessSettingsException::PHP_PATH_IS_NOT_STRING + throw TargetPhpInspectorSettingsException::create( + TargetPhpInspectorSettingsException::PHP_PATH_IS_NOT_STRING ); } $libpthread_path = $input->getOption('libpthread-path'); if (!is_null($libpthread_path) and !is_string($libpthread_path)) { - throw TargetProcessSettingsException::create( - TargetProcessSettingsException::LIBPTHREAD_PATH_IS_NOT_STRING + throw TargetPhpInspectorSettingsException::create( + TargetPhpInspectorSettingsException::LIBPTHREAD_PATH_IS_NOT_STRING ); } - return new self($pid, $php_regex, $libpthread_regex, $php_version, $php_path, $libpthread_path); + return new self($php_regex, $libpthread_regex, $php_version, $php_path, $libpthread_path); } } diff --git a/src/Inspector/Settings/TargetProcessInspectorSettingsException.php b/src/Inspector/Settings/TargetProcessInspectorSettingsException.php new file mode 100644 index 00000000..20027b31 --- /dev/null +++ b/src/Inspector/Settings/TargetProcessInspectorSettingsException.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Settings; + +final class TargetProcessInspectorSettingsException extends InspectorSettingsException +{ + public const PID_NOT_SPECIFIED = 1; + public const PID_IS_NOT_INTEGER = 2; + + protected const ERRORS = [ + self::PID_NOT_SPECIFIED => 'pid is not specified', + self::PID_IS_NOT_INTEGER => 'pid is not integer', + ]; +} diff --git a/src/Inspector/Settings/TargetProcessSettings.php b/src/Inspector/Settings/TargetProcessSettings.php new file mode 100644 index 00000000..1c8012fa --- /dev/null +++ b/src/Inspector/Settings/TargetProcessSettings.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Settings; + +use PhpProfiler\Inspector\Settings\InspectorSettingsException; +use PhpProfiler\Lib\PhpInternals\ZendTypeReader; +use Symfony\Component\Console\Input\InputInterface; + +final class TargetProcessSettings +{ + public int $pid; + + /** + * GetTraceSettings constructor. + * @param int $pid + */ + public function __construct(int $pid) + { + $this->pid = $pid; + } + + /** + * @param InputInterface $input + * @return self + * @throws InspectorSettingsException + */ + public static function fromConsoleInput(InputInterface $input): self + { + $pid = $input->getOption('pid'); + if (is_null($pid)) { + throw TargetProcessInspectorSettingsException::create( + TargetProcessInspectorSettingsException::PID_NOT_SPECIFIED + ); + } + $pid = filter_var($pid, FILTER_VALIDATE_INT); + if ($pid === false) { + throw TargetProcessInspectorSettingsException::create( + TargetProcessInspectorSettingsException::PID_NOT_SPECIFIED + ); + } + + return new self($pid); + } +} diff --git a/src/Command/Inspector/Settings/LoopSettingsException.php b/src/Inspector/Settings/TraceLoopInspectorSettingsException.php similarity index 76% rename from src/Command/Inspector/Settings/LoopSettingsException.php rename to src/Inspector/Settings/TraceLoopInspectorSettingsException.php index b9f01da2..99891ade 100644 --- a/src/Command/Inspector/Settings/LoopSettingsException.php +++ b/src/Inspector/Settings/TraceLoopInspectorSettingsException.php @@ -11,11 +11,9 @@ declare(strict_types=1); -namespace PhpProfiler\Command\Inspector\Settings; +namespace PhpProfiler\Inspector\Settings; -use PhpProfiler\Command\CommandSettingsException; - -class LoopSettingsException extends CommandSettingsException +final class TraceLoopInspectorSettingsException extends InspectorSettingsException { public const SLEEP_NS_IS_NOT_INTEGER = 1; public const MAX_RETRY_IS_NOT_INTEGER = 2; diff --git a/src/Command/Inspector/Settings/LoopSettings.php b/src/Inspector/Settings/TraceLoopSettings.php similarity index 79% rename from src/Command/Inspector/Settings/LoopSettings.php rename to src/Inspector/Settings/TraceLoopSettings.php index 9b763c0c..8f47a176 100644 --- a/src/Command/Inspector/Settings/LoopSettings.php +++ b/src/Inspector/Settings/TraceLoopSettings.php @@ -11,16 +11,15 @@ declare(strict_types=1); -namespace PhpProfiler\Command\Inspector\Settings; +namespace PhpProfiler\Inspector\Settings; -use PhpProfiler\Command\CommandSettingsException; use Symfony\Component\Console\Input\InputInterface; -class LoopSettings +final class TraceLoopSettings { private const SLEEP_NANO_SECONDS_DEFAULT = 1000 * 1000 * 10; private const CANCEL_KEY_DEFAULT = 'q'; - private const MAX_RETRY_DEFAULT = -1; + private const MAX_RETRY_DEFAULT = 10; public int $sleep_nano_seconds; public string $cancel_key; @@ -42,7 +41,7 @@ public function __construct(int $sleep_nano_seconds, string $cancel_key, int $ma /** * @param InputInterface $input * @return self - * @throws CommandSettingsException + * @throws InspectorSettingsException */ public static function fromConsoleInput(InputInterface $input): self { @@ -52,7 +51,9 @@ public static function fromConsoleInput(InputInterface $input): self } $sleep_nano_seconds = filter_var($sleep_nano_seconds, FILTER_VALIDATE_INT); if ($sleep_nano_seconds === false) { - throw LoopSettingsException::create(LoopSettingsException::SLEEP_NS_IS_NOT_INTEGER); + throw TraceLoopInspectorSettingsException::create( + TraceLoopInspectorSettingsException::SLEEP_NS_IS_NOT_INTEGER + ); } $max_retries = $input->getOption('max-retries'); @@ -61,7 +62,9 @@ public static function fromConsoleInput(InputInterface $input): self } $max_retries = filter_var($max_retries, FILTER_VALIDATE_INT); if ($max_retries === false) { - throw LoopSettingsException::create(LoopSettingsException::MAX_RETRY_IS_NOT_INTEGER); + throw TraceLoopInspectorSettingsException::create( + TraceLoopInspectorSettingsException::MAX_RETRY_IS_NOT_INTEGER + ); } return new self($sleep_nano_seconds, self::CANCEL_KEY_DEFAULT, $max_retries); diff --git a/src/Inspector/TraceLoopProvider.php b/src/Inspector/TraceLoopProvider.php new file mode 100644 index 00000000..b575080e --- /dev/null +++ b/src/Inspector/TraceLoopProvider.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector; + +use PhpProfiler\Inspector\Settings\TraceLoopSettings; +use PhpProfiler\Lib\Loop\Loop; +use PhpProfiler\Lib\Loop\LoopBuilder; +use PhpProfiler\Lib\Loop\LoopMiddleware\CallableMiddleware; +use PhpProfiler\Lib\Loop\LoopMiddleware\KeyboardCancelMiddleware; +use PhpProfiler\Lib\Loop\LoopMiddleware\NanoSleepMiddleware; +use PhpProfiler\Lib\Loop\LoopMiddleware\RetryOnExceptionMiddleware; +use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderException; + +final class TraceLoopProvider +{ + private LoopBuilder $loop_builder; + + public function __construct(LoopBuilder $loop_builder) + { + $this->loop_builder = $loop_builder; + } + + public function getMainLoop(callable $main, TraceLoopSettings $settings): Loop + { + return $this->loop_builder + ->addProcess(RetryOnExceptionMiddleware::class, [$settings->max_retries, [MemoryReaderException::class]]) + ->addProcess(KeyboardCancelMiddleware::class, [$settings->cancel_key]) + ->addProcess(NanoSleepMiddleware::class, [$settings->sleep_nano_seconds]) + ->addProcess(CallableMiddleware::class, [$main]) + ->build(); + } +} diff --git a/src/Lib/Loop/AsyncLoop.php b/src/Lib/Loop/AsyncLoop.php new file mode 100644 index 00000000..c477f0ce --- /dev/null +++ b/src/Lib/Loop/AsyncLoop.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Loop; + +final class AsyncLoop +{ + private AsyncLoopMiddlewareInterface $process; + + public function __construct(AsyncLoopMiddlewareInterface $process) + { + $this->process = $process; + } + + public function invoke(): \Generator + { + while (1) { + $result = $this->process->invoke(); + if (!$result->valid()) { + break; + } + yield from $result; + } + } +} diff --git a/src/Lib/Loop/AsyncLoopBuilder.php b/src/Lib/Loop/AsyncLoopBuilder.php new file mode 100644 index 00000000..3cf4ea8a --- /dev/null +++ b/src/Lib/Loop/AsyncLoopBuilder.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace PhpProfiler\Lib\Loop; + +use LogicException; + +final class AsyncLoopBuilder +{ + /** @var array> */ + private array $process_stack = []; + /** @var array */ + private array $parameter_stack = []; + + /** + * @param class-string $process + * @param array $parameters + * @return self + */ + public function addProcess(string $process, array $parameters): self + { + if (!is_a($process, AsyncLoopMiddlewareInterface::class, true)) { + throw new LogicException('1st argument must be a name of a class implements LoopMiddlewareInterface'); + } + $self = clone $this; + $self->process_stack[] = $process; + $self->parameter_stack[] = $parameters; + return $self; + } + + public function build(): AsyncLoop + { + $process = null; + $stack_num = count($this->process_stack); + for ($i = $stack_num - 1; $i >= 0; $i--) { + $parameters = $this->parameter_stack[$i]; + if (!is_null($process)) { + $parameters[] = $process; + } + $loop_class_name = $this->process_stack[$i]; + $process = new $loop_class_name(...$parameters); + } + if (is_null($process)) { + throw new LogicException('no LoopProcess specified'); + } + return new AsyncLoop($process); + } +} diff --git a/src/Lib/Loop/AsyncLoopMiddleware/CallableMiddlewareAsync.php b/src/Lib/Loop/AsyncLoopMiddleware/CallableMiddlewareAsync.php new file mode 100644 index 00000000..55854fc8 --- /dev/null +++ b/src/Lib/Loop/AsyncLoopMiddleware/CallableMiddlewareAsync.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Loop\AsyncLoopMiddleware; + +use PhpProfiler\Lib\Loop\AsyncLoopMiddlewareInterface; + +final class CallableMiddlewareAsync implements AsyncLoopMiddlewareInterface +{ + private $callable; + + public function __construct(callable $callable) + { + $this->callable = $callable; + } + + public function invoke(): \Generator + { + /** @var bool */ + yield from ($this->callable)(); + } +} diff --git a/src/Lib/Loop/AsyncLoopMiddleware/NanoSleepMiddlewareAsync.php b/src/Lib/Loop/AsyncLoopMiddleware/NanoSleepMiddlewareAsync.php new file mode 100644 index 00000000..990c75c3 --- /dev/null +++ b/src/Lib/Loop/AsyncLoopMiddleware/NanoSleepMiddlewareAsync.php @@ -0,0 +1,34 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Loop\AsyncLoopMiddleware; + +use PhpProfiler\Lib\Loop\AsyncLoopMiddlewareInterface; + +final class NanoSleepMiddlewareAsync implements AsyncLoopMiddlewareInterface +{ + private int $sleep_nano_seconds; + private AsyncLoopMiddlewareInterface $chain; + + public function __construct(int $sleep_nano_seconds, AsyncLoopMiddlewareInterface $chain) + { + $this->sleep_nano_seconds = $sleep_nano_seconds; + $this->chain = $chain; + } + + public function invoke(): \Generator + { + time_nanosleep(0, $this->sleep_nano_seconds); + yield from $this->chain->invoke(); + } +} diff --git a/src/Lib/Loop/AsyncLoopMiddleware/RetryOnExceptionMiddlewareAsync.php b/src/Lib/Loop/AsyncLoopMiddleware/RetryOnExceptionMiddlewareAsync.php new file mode 100644 index 00000000..ac2da8f2 --- /dev/null +++ b/src/Lib/Loop/AsyncLoopMiddleware/RetryOnExceptionMiddlewareAsync.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Loop\AsyncLoopMiddleware; + +use Exception; +use PhpProfiler\Lib\Loop\AsyncLoopMiddlewareInterface; + +final class RetryOnExceptionMiddlewareAsync implements AsyncLoopMiddlewareInterface +{ + private AsyncLoopMiddlewareInterface $chain; + /** @var array> */ + private array $exception_names; + private int $max_retry; + private int $current_retry_count = 0; + + /** + * RetryOnExceptionLoop constructor. + * @param int $max_retry + * @param array> $exception_names + * @param AsyncLoopMiddlewareInterface $chain + */ + public function __construct(int $max_retry, array $exception_names, AsyncLoopMiddlewareInterface $chain) + { + $this->max_retry = $max_retry; + $this->exception_names = $exception_names; + $this->chain = $chain; + } + + public function invoke(): \Generator + { + while ($this->current_retry_count <= $this->max_retry or $this->max_retry === -1) { + try { + yield from $this->chain->invoke(); + } catch (Exception $e) { + if (in_array(get_class($e), $this->exception_names, true)) { + $this->current_retry_count++; + continue; + } + throw $e; + } + $this->current_retry_count = 0; + } + } +} diff --git a/src/Lib/Loop/AsyncLoopMiddlewareInterface.php b/src/Lib/Loop/AsyncLoopMiddlewareInterface.php new file mode 100644 index 00000000..56bf3dc3 --- /dev/null +++ b/src/Lib/Loop/AsyncLoopMiddlewareInterface.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Loop; + +interface AsyncLoopMiddlewareInterface +{ + public function invoke(): \Generator; +} diff --git a/src/Lib/Loop/Loop.php b/src/Lib/Loop/Loop.php index 8c342e11..0450f849 100644 --- a/src/Lib/Loop/Loop.php +++ b/src/Lib/Loop/Loop.php @@ -15,9 +15,9 @@ final class Loop { - private LoopProcessInterface $process; + private LoopMiddlewareInterface $process; - public function __construct(LoopProcessInterface $process) + public function __construct(LoopMiddlewareInterface $process) { $this->process = $process; } diff --git a/src/Lib/Loop/LoopBuilder.php b/src/Lib/Loop/LoopBuilder.php index 98905837..1bc5e17c 100644 --- a/src/Lib/Loop/LoopBuilder.php +++ b/src/Lib/Loop/LoopBuilder.php @@ -17,20 +17,20 @@ final class LoopBuilder { - /** @var array> */ + /** @var array> */ private array $process_stack = []; /** @var array */ private array $parameter_stack = []; /** - * @param class-string $process + * @param class-string $process * @param array $parameters * @return self */ public function addProcess(string $process, array $parameters): self { - if (!is_a($process, LoopProcessInterface::class, true)) { - throw new LogicException('1st argument must be a name of a class implements LoopProcessInterface'); + if (!is_a($process, LoopMiddlewareInterface::class, true)) { + throw new LogicException('1st argument must be a name of a class implements LoopMiddlewareInterface'); } $self = clone $this; $self->process_stack[] = $process; diff --git a/src/Lib/Loop/LoopProcess/CallableLoop.php b/src/Lib/Loop/LoopMiddleware/CallableMiddleware.php similarity index 75% rename from src/Lib/Loop/LoopProcess/CallableLoop.php rename to src/Lib/Loop/LoopMiddleware/CallableMiddleware.php index 406aa438..939a5241 100644 --- a/src/Lib/Loop/LoopProcess/CallableLoop.php +++ b/src/Lib/Loop/LoopMiddleware/CallableMiddleware.php @@ -11,11 +11,11 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; -use PhpProfiler\Lib\Loop\LoopProcessInterface; +use PhpProfiler\Lib\Loop\LoopMiddlewareInterface; -final class CallableLoop implements LoopProcessInterface +final class CallableMiddleware implements LoopMiddlewareInterface { private $callable; diff --git a/src/Lib/Loop/LoopProcess/KeyboardCancelLoop.php b/src/Lib/Loop/LoopMiddleware/KeyboardCancelMiddleware.php similarity index 72% rename from src/Lib/Loop/LoopProcess/KeyboardCancelLoop.php rename to src/Lib/Loop/LoopMiddleware/KeyboardCancelMiddleware.php index 98386abd..6fce8108 100644 --- a/src/Lib/Loop/LoopProcess/KeyboardCancelLoop.php +++ b/src/Lib/Loop/LoopMiddleware/KeyboardCancelMiddleware.php @@ -11,18 +11,18 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; -use PhpProfiler\Lib\Loop\LoopProcessInterface; +use PhpProfiler\Lib\Loop\LoopMiddlewareInterface; -final class KeyboardCancelLoop implements LoopProcessInterface +final class KeyboardCancelMiddleware implements LoopMiddlewareInterface { - private LoopProcessInterface $chain; + private LoopMiddlewareInterface $chain; private string $cancel_key; /** @var resource */ private $keyboard_input; - public function __construct(string $cancel_key, LoopProcessInterface $chain) + public function __construct(string $cancel_key, LoopMiddlewareInterface $chain) { $this->chain = $chain; exec('stty -icanon -echo'); diff --git a/src/Lib/Loop/LoopProcess/NanoSleepLoop.php b/src/Lib/Loop/LoopMiddleware/NanoSleepMiddleware.php similarity index 66% rename from src/Lib/Loop/LoopProcess/NanoSleepLoop.php rename to src/Lib/Loop/LoopMiddleware/NanoSleepMiddleware.php index 7cd95442..8b711433 100644 --- a/src/Lib/Loop/LoopProcess/NanoSleepLoop.php +++ b/src/Lib/Loop/LoopMiddleware/NanoSleepMiddleware.php @@ -11,16 +11,16 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; -use PhpProfiler\Lib\Loop\LoopProcessInterface; +use PhpProfiler\Lib\Loop\LoopMiddlewareInterface; -final class NanoSleepLoop implements LoopProcessInterface +final class NanoSleepMiddleware implements LoopMiddlewareInterface { private int $sleep_nano_seconds; - private LoopProcessInterface $chain; + private LoopMiddlewareInterface $chain; - public function __construct(int $sleep_nano_seconds, LoopProcessInterface $chain) + public function __construct(int $sleep_nano_seconds, LoopMiddlewareInterface $chain) { $this->sleep_nano_seconds = $sleep_nano_seconds; $this->chain = $chain; @@ -28,10 +28,10 @@ public function __construct(int $sleep_nano_seconds, LoopProcessInterface $chain public function invoke(): bool { + time_nanosleep(0, $this->sleep_nano_seconds); if (!$this->chain->invoke()) { return false; } - time_nanosleep(0, $this->sleep_nano_seconds); return true; } } diff --git a/src/Lib/Loop/LoopProcess/RetryOnExceptionLoop.php b/src/Lib/Loop/LoopMiddleware/RetryOnExceptionMiddleware.php similarity index 81% rename from src/Lib/Loop/LoopProcess/RetryOnExceptionLoop.php rename to src/Lib/Loop/LoopMiddleware/RetryOnExceptionMiddleware.php index a03040e4..2e7316d6 100644 --- a/src/Lib/Loop/LoopProcess/RetryOnExceptionLoop.php +++ b/src/Lib/Loop/LoopMiddleware/RetryOnExceptionMiddleware.php @@ -11,14 +11,14 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; use Exception; -use PhpProfiler\Lib\Loop\LoopProcessInterface; +use PhpProfiler\Lib\Loop\LoopMiddlewareInterface; -final class RetryOnExceptionLoop implements LoopProcessInterface +final class RetryOnExceptionMiddleware implements LoopMiddlewareInterface { - private LoopProcessInterface $chain; + private LoopMiddlewareInterface $chain; /** @var array> */ private array $exception_names; private int $max_retry; @@ -28,9 +28,9 @@ final class RetryOnExceptionLoop implements LoopProcessInterface * RetryOnExceptionLoop constructor. * @param int $max_retry * @param array> $exception_names - * @param LoopProcessInterface $chain + * @param LoopMiddlewareInterface $chain */ - public function __construct(int $max_retry, array $exception_names, LoopProcessInterface $chain) + public function __construct(int $max_retry, array $exception_names, LoopMiddlewareInterface $chain) { $this->max_retry = $max_retry; $this->exception_names = $exception_names; diff --git a/src/Lib/Loop/LoopProcessInterface.php b/src/Lib/Loop/LoopMiddlewareInterface.php similarity index 90% rename from src/Lib/Loop/LoopProcessInterface.php rename to src/Lib/Loop/LoopMiddlewareInterface.php index f28b2c27..9620d82c 100644 --- a/src/Lib/Loop/LoopProcessInterface.php +++ b/src/Lib/Loop/LoopMiddlewareInterface.php @@ -13,7 +13,7 @@ namespace PhpProfiler\Lib\Loop; -interface LoopProcessInterface +interface LoopMiddlewareInterface { public function invoke(): bool; } diff --git a/src/Lib/PhpProcessReader/PhpGlobalsFinder.php b/src/Lib/PhpProcessReader/PhpGlobalsFinder.php index e5b681ef..f4743573 100644 --- a/src/Lib/PhpProcessReader/PhpGlobalsFinder.php +++ b/src/Lib/PhpProcessReader/PhpGlobalsFinder.php @@ -13,7 +13,8 @@ namespace PhpProfiler\Lib\PhpProcessReader; -use PhpProfiler\Command\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TargetProcessSettings; use PhpProfiler\Lib\ByteStream\IntegerByteSequence\IntegerByteSequenceReader; use PhpProfiler\Lib\ByteStream\CDataByteReader; use PhpProfiler\Lib\Elf\Parser\ElfParserException; @@ -57,16 +58,21 @@ public function __construct( /** * @param TargetProcessSettings $target_process_settings + * @param TargetPhpSettings $target_php_settings * @return int - * @throws ElfParserException * @throws MemoryReaderException * @throws ProcessSymbolReaderException * @throws TlsFinderException */ - public function findTsrmLsCache(TargetProcessSettings $target_process_settings): ?int - { + public function findTsrmLsCache( + TargetProcessSettings $target_process_settings, + TargetPhpSettings $target_php_settings + ): ?int { if (!isset($this->tsrm_ls_cache) and !$this->tsrm_ls_cache_not_found) { - $tsrm_lm_cache_cdata = $this->getSymbolReader($target_process_settings)->read('_tsrm_ls_cache'); + $tsrm_lm_cache_cdata = $this->getSymbolReader( + $target_process_settings, + $target_php_settings + )->read('_tsrm_ls_cache'); if (isset($tsrm_lm_cache_cdata)) { $this->tsrm_ls_cache = $this->integer_reader->read64( new CDataByteReader($tsrm_lm_cache_cdata), @@ -81,21 +87,23 @@ public function findTsrmLsCache(TargetProcessSettings $target_process_settings): /** * @param TargetProcessSettings $target_process_settings + * @param TargetPhpSettings $target_php_settings * @return ProcessSymbolReaderInterface - * @throws ElfParserException * @throws MemoryReaderException * @throws ProcessSymbolReaderException * @throws TlsFinderException */ - public function getSymbolReader(TargetProcessSettings $target_process_settings): ProcessSymbolReaderInterface - { + public function getSymbolReader( + TargetProcessSettings $target_process_settings, + TargetPhpSettings $target_php_settings + ): ProcessSymbolReaderInterface { if (!isset($this->php_symbol_reader_cache[$target_process_settings->pid])) { $symbol_reader = $this->php_symbol_reader_creator->create( $target_process_settings->pid, - $target_process_settings->php_regex, - $target_process_settings->libpthread_regex, - $target_process_settings->php_path, - $target_process_settings->libpthread_path + $target_php_settings->php_regex, + $target_php_settings->libpthread_regex, + $target_php_settings->php_path, + $target_php_settings->libpthread_path ); $this->php_symbol_reader_cache[$target_process_settings->pid] = $symbol_reader; } @@ -104,22 +112,25 @@ public function getSymbolReader(TargetProcessSettings $target_process_settings): /** * @param TargetProcessSettings $target_process_settings + * @param TargetPhpSettings $target_php_settings * @return int * @throws ElfParserException * @throws MemoryReaderException * @throws ProcessSymbolReaderException * @throws TlsFinderException */ - public function findExecutorGlobals(TargetProcessSettings $target_process_settings): int - { - $tsrm_ls_cache = $this->findTsrmLsCache($target_process_settings); + public function findExecutorGlobals( + TargetProcessSettings $target_process_settings, + TargetPhpSettings $target_php_settings + ): int { + $tsrm_ls_cache = $this->findTsrmLsCache($target_process_settings, $target_php_settings); if (isset($tsrm_ls_cache)) { - switch ($target_process_settings->php_version) { + switch ($target_php_settings->php_version) { case ZendTypeReader::V70: case ZendTypeReader::V71: case ZendTypeReader::V72: case ZendTypeReader::V73: - $executor_globals_id_cdata = $this->getSymbolReader($target_process_settings) + $executor_globals_id_cdata = $this->getSymbolReader($target_process_settings, $target_php_settings) ->read('executor_globals_id'); if (is_null($executor_globals_id_cdata)) { throw new RuntimeException('executor_globals_id not found'); @@ -150,8 +161,10 @@ public function findExecutorGlobals(TargetProcessSettings $target_process_settin )->toInt(); case ZendTypeReader::V74: - $executor_globals_offset_cdata = $this->getSymbolReader($target_process_settings) - ->read('executor_globals_offset'); + $executor_globals_offset_cdata = $this->getSymbolReader( + $target_process_settings, + $target_php_settings + )->read('executor_globals_offset'); if (is_null($executor_globals_offset_cdata)) { throw new RuntimeException('executor_globals_offset not found'); } @@ -164,7 +177,7 @@ public function findExecutorGlobals(TargetProcessSettings $target_process_settin throw new \LogicException('this should never happen'); } } - $executor_globals_address = $this->getSymbolReader($target_process_settings) + $executor_globals_address = $this->getSymbolReader($target_process_settings, $target_php_settings) ->resolveAddress('executor_globals'); if (is_null($executor_globals_address)) { throw new RuntimeException('executor globals not found'); diff --git a/src/Lib/Process/ProcFileSystem/CommandLineEnumerator.php b/src/Lib/Process/ProcFileSystem/CommandLineEnumerator.php new file mode 100644 index 00000000..adb89294 --- /dev/null +++ b/src/Lib/Process/ProcFileSystem/CommandLineEnumerator.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Process\ProcFileSystem; + +use IteratorAggregate; +use PhpProfiler\Lib\File\FileReaderInterface; + +final class CommandLineEnumerator implements IteratorAggregate +{ + private FileReaderInterface $reader; + + public function __construct(FileReaderInterface $reader) + { + $this->reader = $reader; + } + + /** + * @return \Generator + */ + public function getIterator() + { + /** + * @var string $full_path + * @var \SplFileInfo $item + */ + foreach (new \GlobIterator('/proc/*/cmdline') as $full_path => $item) { + $command_line = $this->reader->readAll($full_path); + if ($command_line === '') { + continue; + } + + yield (int)basename($item->getPath()) => $command_line; + } + } +} diff --git a/src/Lib/Process/Search/ProcessSearcher.php b/src/Lib/Process/Search/ProcessSearcher.php new file mode 100644 index 00000000..0af3ff48 --- /dev/null +++ b/src/Lib/Process/Search/ProcessSearcher.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Process\Search; + +use PhpProfiler\Lib\File\CatFileReader; +use PhpProfiler\Lib\File\FileReaderInterface; +use PhpProfiler\Lib\Process\ProcFileSystem\CommandLineEnumerator; + +final class ProcessSearcher +{ + /** + * @var FileReaderInterface + */ + private FileReaderInterface $file_reader; + + public function __construct(FileReaderInterface $file_reader) + { + $this->file_reader = $file_reader; + } + + /** + * @param string $regex + * @return int[] + */ + public function searchByRegex(string $regex): array + { + $result = []; + + foreach (new CommandLineEnumerator($this->file_reader) as $pid => $command_line) { + if (preg_match($regex, $command_line)) { + $result[] = $pid; + } + } + + return $result; + } +} diff --git a/tests/Inspector/Daemon/Reader/Context/PhpReaderContextTest.php b/tests/Inspector/Daemon/Reader/Context/PhpReaderContextTest.php new file mode 100644 index 00000000..1d55c78a --- /dev/null +++ b/tests/Inspector/Daemon/Reader/Context/PhpReaderContextTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Inspector\Daemon\Reader\Context; + +use Amp\Parallel\Context\Context; +use Amp\Promise; +use Mockery; +use PhpProfiler\Inspector\Daemon\Reader\Message\SetSettingsMessage; +use PhpProfiler\Inspector\Settings\GetTraceSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TraceLoopSettings; +use PHPUnit\Framework\TestCase; + +final class PhpReaderContextTest extends TestCase +{ + public function testStart(): void + { + $context = Mockery::mock(Context::class); + $context->expects()->start()->andReturn(Mockery::mock(Promise::class)); + $php_reader_context = new PhpReaderContext($context); + $this->assertInstanceOf(Promise::class, $php_reader_context->start()); + } + + public function testIsRunning(): void + { + $context = Mockery::mock(Context::class); + $context->expects()->isRunning()->andReturn(true); + $context->expects()->isRunning()->andReturn(false); + $php_reader_context = new PhpReaderContext($context); + $this->assertSame(true, $php_reader_context->isRunning()); + $this->assertSame(false, $php_reader_context->isRunning()); + } + + public function testSendSettings(): void + { + $target_php_settings = new TargetPhpSettings(); + $trace_loop_settings = new TraceLoopSettings(1, 'q', 1); + $get_trace_settings = new GetTraceSettings(1); + + $expected = new SetSettingsMessage( + $target_php_settings, + $trace_loop_settings, + $get_trace_settings + ); + + $context = Mockery::mock(Context::class); + $context->shouldReceive('send') + ->once() + ->with( + Mockery::on(function (SetSettingsMessage $actual) use ($expected) { + $this->assertEquals($actual, $expected); + return true; + }) + ) + ->andReturn(Mockery::mock(Promise::class)); + $php_reader_context = new PhpReaderContext($context); + $this->assertInstanceOf( + Promise::class, + $php_reader_context->sendSettings( + $target_php_settings, + $trace_loop_settings, + $get_trace_settings + ) + ); + } + + public function testReceiveTrace(): void + { + $context = Mockery::mock(Context::class); + $context->expects()->receive()->andReturn(Mockery::mock(Promise::class)); + $php_reader_context = new PhpReaderContext($context); + $this->assertInstanceOf(Promise::class, $php_reader_context->receiveTrace()); + } +} diff --git a/tests/Lib/Loop/LoopBuilderTest.php b/tests/Lib/Loop/LoopBuilderTest.php index ca8b26a1..254cdc4a 100644 --- a/tests/Lib/Loop/LoopBuilderTest.php +++ b/tests/Lib/Loop/LoopBuilderTest.php @@ -15,8 +15,8 @@ use Exception; use LogicException; -use PhpProfiler\Lib\Loop\LoopProcess\CallableLoop; -use PhpProfiler\Lib\Loop\LoopProcess\RetryOnExceptionLoop; +use PhpProfiler\Lib\Loop\LoopMiddleware\CallableMiddleware; +use PhpProfiler\Lib\Loop\LoopMiddleware\RetryOnExceptionMiddleware; use PHPUnit\Framework\TestCase; class LoopBuilderTest extends TestCase @@ -26,9 +26,9 @@ public function testBuild(): void $call_counter = 0; $execute_counter = 0; $builder = new LoopBuilder(); - $loop = $builder->addProcess(RetryOnExceptionLoop::class, [1, [Exception::class]]) + $loop = $builder->addProcess(RetryOnExceptionMiddleware::class, [1, [Exception::class]]) ->addProcess( - CallableLoop::class, + CallableMiddleware::class, [ function () use (&$call_counter, &$execute_counter): bool { if (++$call_counter === 1) { diff --git a/tests/Lib/Loop/LoopProcess/CallableLoopTest.php b/tests/Lib/Loop/LoopMiddleware/CallableLoopTest.php similarity index 82% rename from tests/Lib/Loop/LoopProcess/CallableLoopTest.php rename to tests/Lib/Loop/LoopMiddleware/CallableLoopTest.php index 2b747fa8..6fac89bc 100644 --- a/tests/Lib/Loop/LoopProcess/CallableLoopTest.php +++ b/tests/Lib/Loop/LoopMiddleware/CallableLoopTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; use PHPUnit\Framework\TestCase; @@ -20,7 +20,7 @@ class CallableLoopTest extends TestCase public function testInvoke(): void { $side_effect = false; - $loop = new CallableLoop(function () use (&$side_effect) { + $loop = new CallableMiddleware(function () use (&$side_effect) { $side_effect = true; return true; }); diff --git a/tests/Lib/Loop/LoopProcess/KeyboardCancelLoopTest.php b/tests/Lib/Loop/LoopMiddleware/KeyboardCancelLoopTest.php similarity index 81% rename from tests/Lib/Loop/LoopProcess/KeyboardCancelLoopTest.php rename to tests/Lib/Loop/LoopMiddleware/KeyboardCancelLoopTest.php index 19f68132..c715eaa7 100644 --- a/tests/Lib/Loop/LoopProcess/KeyboardCancelLoopTest.php +++ b/tests/Lib/Loop/LoopMiddleware/KeyboardCancelLoopTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; use PHPUnit\Framework\TestCase; use ReflectionClass; @@ -24,12 +24,12 @@ class KeyboardCancelLoopTest extends TestCase */ public function testReturnFalseIfCancelKeyPressed(): void { - $reflection = new ReflectionClass(KeyboardCancelLoop::class); + $reflection = new ReflectionClass(KeyboardCancelMiddleware::class); $keyboard_cancel_loop = $reflection->newInstanceWithoutConstructor(); $keyboard_input_stream = fopen('php://memory', 'rw'); (function () use ($keyboard_input_stream) { - /** @var KeyboardCancelLoop $this */ - $this->chain = new CallableLoop(fn () => true); + /** @var KeyboardCancelMiddleware $this */ + $this->chain = new CallableMiddleware(fn () => true); $this->cancel_key = 'q'; $this->keyboard_input = $keyboard_input_stream; })->bindTo($keyboard_cancel_loop, $keyboard_cancel_loop)(); diff --git a/tests/Lib/Loop/LoopProcess/NanoSleepLoopTest.php b/tests/Lib/Loop/LoopMiddleware/NanoSleepLoopTest.php similarity index 58% rename from tests/Lib/Loop/LoopProcess/NanoSleepLoopTest.php rename to tests/Lib/Loop/LoopMiddleware/NanoSleepLoopTest.php index 44e2acb9..7c2be7d8 100644 --- a/tests/Lib/Loop/LoopProcess/NanoSleepLoopTest.php +++ b/tests/Lib/Loop/LoopMiddleware/NanoSleepLoopTest.php @@ -11,27 +11,30 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; +use LogicException; use PHPUnit\Framework\TestCase; class NanoSleepLoopTest extends TestCase { - public function testReturnFalseWithoutSleepIfChainFailed(): void + public function testReturnFalseIfChainFailed(): void { $time = time(); - $nano_sleep_loop = new NanoSleepLoop( - 1000 * 1000 * 1000, - new CallableLoop(fn () => false) + $nano_sleep_loop = new NanoSleepMiddleware( + 0, + new CallableMiddleware(fn () => false) ); $this->assertSame(false, $nano_sleep_loop->invoke()); } - public function testSleepIfChainSucceed(): void + public function testSleepBeforeChainInvoked(): void { - $nano_sleep_loop = new NanoSleepLoop( + $nano_sleep_loop = new NanoSleepMiddleware( 1000 * 1000 * 1000, - new CallableLoop(fn () => true) + new CallableMiddleware(function () { + throw new LogicException('should not be thrown'); + }) ); $this->expectWarning(); $this->expectWarningMessageMatches('/nanoseconds was not in the range 0 to 999 999 999/'); @@ -40,9 +43,9 @@ public function testSleepIfChainSucceed(): void public function testReturnTrueIfChainSucceed(): void { - $nano_sleep_loop = new NanoSleepLoop( + $nano_sleep_loop = new NanoSleepMiddleware( 0, - new CallableLoop(fn () => true) + new CallableMiddleware(fn () => true) ); $this->assertSame(true, $nano_sleep_loop->invoke()); } diff --git a/tests/Lib/Loop/LoopProcess/RetryOnExceptionLoopTest.php b/tests/Lib/Loop/LoopMiddleware/RetryOnExceptionLoopTest.php similarity index 77% rename from tests/Lib/Loop/LoopProcess/RetryOnExceptionLoopTest.php rename to tests/Lib/Loop/LoopMiddleware/RetryOnExceptionLoopTest.php index b696c964..e3821f55 100644 --- a/tests/Lib/Loop/LoopProcess/RetryOnExceptionLoopTest.php +++ b/tests/Lib/Loop/LoopMiddleware/RetryOnExceptionLoopTest.php @@ -11,7 +11,7 @@ declare(strict_types=1); -namespace PhpProfiler\Lib\Loop\LoopProcess; +namespace PhpProfiler\Lib\Loop\LoopMiddleware; use Exception; use LogicException; @@ -22,19 +22,19 @@ class RetryOnExceptionLoopTest extends TestCase { public function testReturnIfChainReturn(): void { - $loop = new RetryOnExceptionLoop(0, [Exception::class], new CallableLoop(fn () => true)); + $loop = new RetryOnExceptionMiddleware(0, [Exception::class], new CallableMiddleware(fn () => true)); $this->assertSame(true, $loop->invoke()); - $loop = new RetryOnExceptionLoop(0, [Exception::class], new CallableLoop(fn () => false)); + $loop = new RetryOnExceptionMiddleware(0, [Exception::class], new CallableMiddleware(fn () => false)); $this->assertSame(false, $loop->invoke()); } public function testRetryIfChainThrows(): void { $counter = 0; - $loop = new RetryOnExceptionLoop( + $loop = new RetryOnExceptionMiddleware( 1, [Exception::class], - new CallableLoop( + new CallableMiddleware( function () use (&$counter) { if ($counter++ === 0) { throw new Exception(); @@ -50,10 +50,10 @@ function () use (&$counter) { public function testReturnFalseIfRetryCountExceedsMax(): void { $counter = 0; - $loop = new RetryOnExceptionLoop( + $loop = new RetryOnExceptionMiddleware( 0, [Exception::class], - new CallableLoop( + new CallableMiddleware( function () use (&$counter) { if ($counter++ === 0) { throw new Exception(); @@ -68,10 +68,10 @@ function () use (&$counter) { public function testRethrowUnspecifiedException(): void { - $loop = new RetryOnExceptionLoop( + $loop = new RetryOnExceptionMiddleware( 0, [RuntimeException::class], - new CallableLoop( + new CallableMiddleware( function () { throw new LogicException(); } diff --git a/tests/Lib/Loop/LoopTest.php b/tests/Lib/Loop/LoopTest.php index aac08205..afff33a5 100644 --- a/tests/Lib/Loop/LoopTest.php +++ b/tests/Lib/Loop/LoopTest.php @@ -13,7 +13,7 @@ namespace PhpProfiler\Lib\Loop; -use PhpProfiler\Lib\Loop\LoopProcess\CallableLoop; +use PhpProfiler\Lib\Loop\LoopMiddleware\CallableMiddleware; use PHPUnit\Framework\TestCase; class LoopTest extends TestCase @@ -22,7 +22,7 @@ public function testInvoke() { $counter = 0; $loop = new Loop( - new CallableLoop( + new CallableMiddleware( function () use (&$counter) { $counter++; if ($counter >= 3) { diff --git a/tests/Lib/PhpProcessReader/PhpMemoryReader/ExecutorGlobalsReaderTest.php b/tests/Lib/PhpProcessReader/PhpMemoryReader/ExecutorGlobalsReaderTest.php index 0724aa84..b26e4a29 100644 --- a/tests/Lib/PhpProcessReader/PhpMemoryReader/ExecutorGlobalsReaderTest.php +++ b/tests/Lib/PhpProcessReader/PhpMemoryReader/ExecutorGlobalsReaderTest.php @@ -13,7 +13,8 @@ namespace PhpProfiler\Lib\PhpProcessReader\PhpMemoryReader; -use PhpProfiler\Command\Inspector\Settings\TargetProcessSettings; +use PhpProfiler\Inspector\Settings\TargetPhpSettings; +use PhpProfiler\Inspector\Settings\TargetProcessSettings; use PhpProfiler\Lib\ByteStream\IntegerByteSequence\LittleEndianReader; use PhpProfiler\Lib\Elf\Parser\Elf64Parser; use PhpProfiler\Lib\Elf\Process\ProcessModuleSymbolReaderCreator; @@ -89,7 +90,8 @@ public function testReadCurrentFunctionName() /** @var int $child_status['pid'] */ $executor_globals_address = $php_globals_finder->findExecutorGlobals( - new TargetProcessSettings($child_status['pid']) + new TargetProcessSettings($child_status['pid']), + new TargetPhpSettings() ); $name = $executor_globals_reader->readCurrentFunctionName( $child_status['pid'], diff --git a/tests/Lib/Process/MemoryMap/ProcessMemoryMapReaderTest.php b/tests/Lib/Process/MemoryMap/ProcessMemoryMapReaderTest.php index 7a1ac748..9d6ce8a4 100644 --- a/tests/Lib/Process/MemoryMap/ProcessMemoryMapReaderTest.php +++ b/tests/Lib/Process/MemoryMap/ProcessMemoryMapReaderTest.php @@ -25,7 +25,7 @@ public function testRead() { $result = (new ProcessMemoryMapReader())->read(getmypid()); $first_line = strtok($result, "\n"); - $this->assertRegExp( + $this->assertMatchesRegularExpression( '/[0-9a-f]{12,16}-[0-9a-f]{12,16} [r\-][w\-][x\-][p\-] [0-9a-f]{8} [0-9][0-9]:[0-9][0-9] [0-9]+ +[^ ]+/', $first_line ); diff --git a/tests/Lib/Process/ProcFileSystem/CommandLineEnumeratorTest.php b/tests/Lib/Process/ProcFileSystem/CommandLineEnumeratorTest.php new file mode 100644 index 00000000..0c2332e6 --- /dev/null +++ b/tests/Lib/Process/ProcFileSystem/CommandLineEnumeratorTest.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Process\ProcFileSystem; + +use PhpProfiler\Lib\File\CatFileReader; +use PHPUnit\Framework\TestCase; + +class CommandLineEnumeratorTest extends TestCase +{ + /** @var resource|null */ + private $child = null; + + public function tearDown(): void + { + if (!is_null($this->child)) { + $child_status = proc_get_status($this->child); + if (is_array($child_status)) { + if ($child_status['running']) { + posix_kill($child_status['pid'], SIGKILL); + } + } + } + } + + public function testGetIterator() + { + $this->child = proc_open( + [ + PHP_BINARY, + '-r', + 'fputs(STDOUT, "a\n");fgets(STDIN);' + ], + [ + ['pipe', 'r'], + ['pipe', 'w'], + ['pipe', 'w'] + ], + $pipes + ); + fgets($pipes[1]); + $child_status = proc_get_status($this->child); + $child_pid = $child_status['pid']; + + $target = null; + foreach (new CommandLineEnumerator(new CatFileReader()) as $pid => $command_line) { + if ($pid === $child_pid) { + $target = $command_line; + break; + } + } + $this->assertNotNull($target); + $this->assertStringContainsString(PHP_BINARY, $target); + $this->assertStringContainsString('fputs(STDOUT, "a\n");fgets(STDIN);', $target); + } +} diff --git a/tests/Lib/Process/Search/ProcessSearcherTest.php b/tests/Lib/Process/Search/ProcessSearcherTest.php new file mode 100644 index 00000000..bd89df6d --- /dev/null +++ b/tests/Lib/Process/Search/ProcessSearcherTest.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +declare(strict_types=1); + +namespace PhpProfiler\Lib\Process\Search; + +use PhpProfiler\Lib\File\CatFileReader; +use PHPUnit\Framework\TestCase; + +class ProcessSearcherTest extends TestCase +{ + /** @var resource|null */ + private $child = null; + + public function tearDown(): void + { + if (!is_null($this->child)) { + $child_status = proc_get_status($this->child); + if (is_array($child_status)) { + if ($child_status['running']) { + posix_kill($child_status['pid'], SIGKILL); + } + } + } + } + + public function testSearch() + { + $this->child = proc_open( + [ + PHP_BINARY, + '-r', + 'fputs(STDOUT, "test_ProcessSearcherTest`\n");fgets(STDIN);' + ], + [ + ['pipe', 'r'], + ['pipe', 'w'], + ['pipe', 'w'] + ], + $pipes + ); + fgets($pipes[1]); + $child_status = proc_get_status($this->child); + $child_pid = $child_status['pid']; + + $searcher = new ProcessSearcher(new CatFileReader()); + $this->assertSame( + [$child_pid], + $searcher->searchByRegex('/test_ProcessSearcherTest/') + ); + } +}