diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 91ccbe6..fb1d4a1 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -7,3 +7,26 @@ on: jobs: ci: uses: laminas/workflow-continuous-integration/.github/workflows/continuous-integration.yml@1.x + + windows-tests: + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: + - windows-latest + php-versions: + - '8.1' + - '8.2' + - '8.3' + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: intl, sodium, zip + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v4 + - uses: ramsey/composer-install@v3 + - name: Run PHPUnit + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore index 1d5a365..21c8f73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /vendor/ /psalm.xml /.phpunit.cache/ +/.phpunit.result.cache /.phpcs-cache diff --git a/.phpstorm.meta.php b/.phpstorm.meta.php new file mode 100644 index 0000000..05552cf --- /dev/null +++ b/.phpstorm.meta.php @@ -0,0 +1,9 @@ + '@', + ]) + ); +} diff --git a/README.md b/README.md index 19b1baa..e9c4a00 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,51 @@ # 🥧 PIE (PHP Installer for Extensions) -(to be completed...) +You will need PHP 8.1 or newer to run PIE, but PIE can install an extension to any installed PHP version. ## Installing ## Usage +You can download an extension ready to be built or installed using the `download` command. For example, to download the +`example_pie_extension` extension, you would run: + +```shell +$ bin/pie download asgrim/example-pie-extension +You are running PHP 8.3.7 +Target PHP installation: 8.3.7 nts, on Linux/OSX/etc x86_64 (from /usr/bin/php8.3) +Found package: asgrim/example-pie-extension:1.0.1 which provides ext-example_pie_extension +Extracted asgrim/example-pie-extension:1.0.1 source to: /tmp/pie_downloader_6645f07a28bec9.66045489/asgrim-example-pie-extension-769f906 +$ +``` + +If you are trying to install an extension for a different version of PHP, you may specify this on non-Windows systems +with the `--with-php-config` option like: + +```shell +bin/pie download --with-php-config=/usr/bin/php-config7.2 my/extension +``` + +On all platforms, you may provide a path to the `php` executable itself using the `--with-php-path` option. This is an +example on Windows where PHP 8.1 is used to run PIE, but we want to download the extension for PHP 8.3: + +```shell +> C:\php-8.1.7\php.exe bin/pie download --with-php-path=C:\php-8.3.6\php.exe asgrim/example-pie-extension +You are running PHP 8.1.7 +Target PHP installation: 8.3.6 ts, vs16, on Windows x86_64 (from C:\php-8.3.6\php.exe) +Found package: asgrim/example-pie-extension:1.0.1 which provides ext-example_pie_extension +Extracted asgrim/example-pie-extension:1.0.1 source to: C:\path\to\temp\pie_downloader_66547faa7db3d7.06129230 +``` + +And this is a very similar example (using PHP 8.1 to run PIE to download a PHP 8.3 extension) on a non-Windows platform: + +```shell +$ php8.1 bin/pie download --with-php-path=/usr/bin/php8.3 asgrim/example-pie-extension +You are running PHP 8.1.28 +Target PHP installation: 8.3.7 nts, on Linux/OSX/etc x86_64 (from /usr/bin/php8.3) +Found package: asgrim/example-pie-extension:1.0.1 which provides ext-example_pie_extension +Extracted asgrim/example-pie-extension:1.0.1 source to: /tmp/pie_downloader_66547da1e6c685.25242810/asgrim-example-pie-extension-769f906 +``` + ## Developing ### Testing diff --git a/bin/pie b/bin/pie index 6d3eb3e..cbff1ac 100755 --- a/bin/pie +++ b/bin/pie @@ -3,14 +3,24 @@ declare(strict_types=1); -use Php\Pie\Command; +namespace Php\Pie; + +use Php\Pie\Command\DownloadCommand; use Symfony\Component\Console\Application; +use Symfony\Component\Console\CommandLoader\ContainerCommandLoader; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\OutputInterface; /** @psalm-suppress UnresolvableInclude */ include $_composer_autoload_path ?? __DIR__ . '/../vendor/autoload.php'; +$container = Container::factory(); + $application = new Application('pie', 'dev-main'); -$application->addCommands([ - new Command\DownloadCommand(), -]); -$application->run(); +$application->setCommandLoader(new ContainerCommandLoader( + $container, + [ + 'download' => DownloadCommand::class, + ] +)); +$application->run($container->get(InputInterface::class), $container->get(OutputInterface::class)); diff --git a/composer.json b/composer.json index 9603cee..7f50029 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ }, "autoload-dev": { "psr-4": { + "Php\\PieIntegrationTest\\": "test/integration/", "Php\\PieUnitTest\\": "test/unit/" } }, @@ -26,11 +27,22 @@ ], "require": { "php": "8.1.*||8.2.*||8.3.*", - "symfony/console": "^6.4" + "ext-zip": "*", + "azjezz/psl": "^2.9", + "composer/composer": "dev-main@dev", + "guzzlehttp/guzzle": "^7.8", + "guzzlehttp/psr7": "^2.6", + "illuminate/container": "^10.47", + "psr/http-message": "^2.0", + "symfony/console": "^6.4", + "symfony/process": "^6.4", + "webmozart/assert": "^1.11" }, "require-dev": { "doctrine/coding-standard": "^12.0", + "php-standard-library/psalm-plugin": "^2.3", "phpunit/phpunit": "^10.5", + "psalm/plugin-phpunit": "^0.18.4", "vimeo/psalm": "^5.22" }, "config": { diff --git a/composer.lock b/composer.lock index 3d9e038..3437487 100644 --- a/composer.lock +++ b/composer.lock @@ -4,34 +4,2283 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "84a9164241781879ce2a3156ecb3fd35", + "content-hash": "527f1f5b3881b9c5f1def2bc223a385d", "packages": [ + { + "name": "azjezz/psl", + "version": "2.9.1", + "source": { + "type": "git", + "url": "https://github.com/azjezz/psl.git", + "reference": "1ade4f1a99fe07a8e06f8dee596609aa07585422" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/azjezz/psl/zipball/1ade4f1a99fe07a8e06f8dee596609aa07585422", + "reference": "1ade4f1a99fe07a8e06f8dee596609aa07585422", + "shasum": "" + }, + "require": { + "ext-bcmath": "*", + "ext-intl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-sodium": "*", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0", + "revolt/event-loop": "^1.0.1" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.22.0", + "php-coveralls/php-coveralls": "^2.6.0", + "php-standard-library/psalm-plugin": "^2.2.1", + "phpbench/phpbench": "^1.2.14", + "phpunit/phpunit": "^9.6.10", + "roave/infection-static-analysis-plugin": "^1.32.0", + "squizlabs/php_codesniffer": "^3.7.2", + "vimeo/psalm": "^5.13.1" + }, + "suggest": { + "php-standard-library/psalm-plugin": "Psalm integration" + }, + "type": "library", + "extra": { + "thanks": { + "name": "hhvm/hsl", + "url": "https://github.com/hhvm/hsl" + } + }, + "autoload": { + "files": [ + "src/bootstrap.php" + ], + "psr-4": { + "Psl\\": "src/Psl" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "azjezz", + "email": "azjezz@protonmail.com" + } + ], + "description": "PHP Standard Library", + "support": { + "issues": "https://github.com/azjezz/psl/issues", + "source": "https://github.com/azjezz/psl/tree/2.9.1" + }, + "funding": [ + { + "url": "https://github.com/azjezz", + "type": "github" + } + ], + "time": "2024-04-05T05:18:37+00:00" + }, + { + "name": "composer/ca-bundle", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/composer/ca-bundle.git", + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "reference": "0c5ccfcfea312b5c5a190a21ac5cef93f74baf99", + "shasum": "" + }, + "require": { + "ext-openssl": "*", + "ext-pcre": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.10", + "psr/log": "^1.0", + "symfony/phpunit-bridge": "^4.2 || ^5", + "symfony/process": "^4.0 || ^5.0 || ^6.0 || ^7.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\CaBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Lets you find a path to the system CA bundle, and includes a fallback to the Mozilla CA bundle.", + "keywords": [ + "cabundle", + "cacert", + "certificate", + "ssl", + "tls" + ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/ca-bundle/issues", + "source": "https://github.com/composer/ca-bundle/tree/1.5.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-15T14:00:32+00:00" + }, + { + "name": "composer/class-map-generator", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/composer/class-map-generator.git", + "reference": "8286a62d243312ed99b3eee20d5005c961adb311" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/class-map-generator/zipball/8286a62d243312ed99b3eee20d5005c961adb311", + "reference": "8286a62d243312ed99b3eee20d5005c961adb311", + "shasum": "" + }, + "require": { + "composer/pcre": "^2.1 || ^3.1", + "php": "^7.2 || ^8.0", + "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.6", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/filesystem": "^5.4 || ^6", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\ClassMapGenerator\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Utilities to scan PHP code and generate class maps.", + "keywords": [ + "classmap" + ], + "support": { + "issues": "https://github.com/composer/class-map-generator/issues", + "source": "https://github.com/composer/class-map-generator/tree/1.1.1" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-15T12:53:41+00:00" + }, + { + "name": "composer/composer", + "version": "dev-main", + "source": { + "type": "git", + "url": "https://github.com/composer/composer.git", + "reference": "877f1b150ffdc7b894e392ec05aa4a130fbb40ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/composer/zipball/877f1b150ffdc7b894e392ec05aa4a130fbb40ae", + "reference": "877f1b150ffdc7b894e392ec05aa4a130fbb40ae", + "shasum": "" + }, + "require": { + "composer/ca-bundle": "^1.0", + "composer/class-map-generator": "^1.0", + "composer/metadata-minifier": "^1.0", + "composer/pcre": "^2.1 || ^3.1", + "composer/semver": "^3.2.5", + "composer/spdx-licenses": "^1.5.7", + "composer/xdebug-handler": "^2.0.2 || ^3.0.3", + "justinrainbow/json-schema": "^5.2.11", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1.0 || ^2.0 || ^3.0", + "react/promise": "^2.8 || ^3", + "seld/jsonlint": "^1.4", + "seld/phar-utils": "^1.2", + "seld/signal-handler": "^2.0", + "symfony/console": "^5.4.11 || ^6.0.11 || ^7", + "symfony/filesystem": "^5.4 || ^6.0 || ^7", + "symfony/finder": "^5.4 || ^6.0 || ^7", + "symfony/polyfill-php73": "^1.24", + "symfony/polyfill-php80": "^1.24", + "symfony/polyfill-php81": "^1.24", + "symfony/process": "^5.4 || ^6.0 || ^7" + }, + "require-dev": { + "phpstan/phpstan": "^1.9.3", + "phpstan/phpstan-deprecation-rules": "^1", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-strict-rules": "^1", + "phpstan/phpstan-symfony": "^1.2.10", + "symfony/phpunit-bridge": "^6.4.1 || ^7.0.1" + }, + "suggest": { + "ext-openssl": "Enabling the openssl extension allows you to access https URLs for repositories and packages", + "ext-zip": "Enabling the zip extension allows you to unzip archives", + "ext-zlib": "Allow gzip compression of HTTP requests" + }, + "default-branch": true, + "bin": [ + "bin/composer" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.7-dev" + }, + "phpstan": { + "includes": [ + "phpstan/rules.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Composer\\": "src/Composer/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "https://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "homepage": "https://getcomposer.org/", + "keywords": [ + "autoload", + "dependency", + "package" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/composer/issues", + "security": "https://github.com/composer/composer/security/policy", + "source": "https://github.com/composer/composer/tree/main" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-04-29T09:03:27+00:00" + }, + { + "name": "composer/metadata-minifier", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/composer/metadata-minifier.git", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/metadata-minifier/zipball/c549d23829536f0d0e984aaabbf02af91f443207", + "reference": "c549d23829536f0d0e984aaabbf02af91f443207", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "composer/composer": "^2", + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\MetadataMinifier\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Small utility library that handles metadata minification and expansion.", + "keywords": [ + "composer", + "compression" + ], + "support": { + "issues": "https://github.com/composer/metadata-minifier/issues", + "source": "https://github.com/composer/metadata-minifier/tree/1.0.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2021-04-07T13:37:33+00:00" + }, + { + "name": "composer/pcre", + "version": "3.1.3", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "reference": "5b16e25a5355f1f3afdfc2f954a0a80aec4826a8", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-strict-rules": "^1.1", + "symfony/phpunit-bridge": "^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/3.1.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-19T10:26:25+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.0", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.0" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-08-31T09:50:34+00:00" + }, + { + "name": "composer/spdx-licenses", + "version": "1.5.8", + "source": { + "type": "git", + "url": "https://github.com/composer/spdx-licenses.git", + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", + "reference": "560bdcf8deb88ae5d611c80a2de8ea9d0358cc0a", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.55", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Spdx\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "SPDX licenses list and validation library.", + "keywords": [ + "license", + "spdx", + "validator" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/spdx-licenses/issues", + "source": "https://github.com/composer/spdx-licenses/tree/1.5.8" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2023-11-20T07:44:33+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.4", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/4f988f8fdf580d53bdb2d1278fe93d1ed5462255", + "reference": "4f988f8fdf580d53bdb2d1278fe93d1ed5462255", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/3.0.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-03-26T18:29:49+00:00" + }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:19:20+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:05:35+00:00" + }, + { + "name": "illuminate/container", + "version": "v10.48.9", + "source": { + "type": "git", + "url": "https://github.com/illuminate/container.git", + "reference": "ddc26273085fad3c471b2602ad820e0097ff7939" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/container/zipball/ddc26273085fad3c471b2602ad820e0097ff7939", + "reference": "ddc26273085fad3c471b2602ad820e0097ff7939", + "shasum": "" + }, + "require": { + "illuminate/contracts": "^10.0", + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1" + }, + "provide": { + "psr/container-implementation": "1.1|2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Container\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Container package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2023-06-18T09:12:03+00:00" + }, + { + "name": "illuminate/contracts", + "version": "v10.48.9", + "source": { + "type": "git", + "url": "https://github.com/illuminate/contracts.git", + "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/illuminate/contracts/zipball/8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "reference": "8d7152c4a1f5d9cf7da3e8b71f23e4556f6138ac", + "shasum": "" + }, + "require": { + "php": "^8.1", + "psr/container": "^1.1.1|^2.0.1", + "psr/simple-cache": "^1.0|^2.0|^3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "10.x-dev" + } + }, + "autoload": { + "psr-4": { + "Illuminate\\Contracts\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + } + ], + "description": "The Illuminate Contracts package.", + "homepage": "https://laravel.com", + "support": { + "issues": "https://github.com/laravel/framework/issues", + "source": "https://github.com/laravel/framework" + }, + "time": "2024-01-15T18:52:32+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "v5.2.13", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/fbbe7e5d79f618997bc3332a6f49246036c45793", + "reference": "fbbe7e5d79f618997bc3332a6f49246036c45793", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2.2.20||~2.15.1", + "json-schema/json-schema-test-suite": "1.2.0", + "phpunit/phpunit": "^4.8.35" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "support": { + "issues": "https://github.com/justinrainbow/json-schema/issues", + "source": "https://github.com/justinrainbow/json-schema/tree/v5.2.13" + }, + "time": "2023-09-26T02:20:38+00:00" + }, { "name": "psr/container", "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/http-client", + "version": "1.0.3", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", + "shasum": "" + }, + "require": { + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "psr/simple-cache", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/764e0b3939f5ca87cb904f570ef9be2d78a07865", + "reference": "764e0b3939f5ca87cb904f570ef9be2d78a07865", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/3.0.0" + }, + "time": "2021-10-29T13:26:27+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "react/promise", + "version": "v3.1.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "reference": "e563d55d1641de1dea9f5e84f3cccc66d2bfe02c", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v3.1.0" + }, + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-16T16:21:57+00:00" + }, + { + "name": "revolt/event-loop", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/revoltphp/event-loop.git", + "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/revoltphp/event-loop/zipball/25de49af7223ba039f64da4ae9a28ec2d10d0254", + "reference": "25de49af7223ba039f64da4ae9a28ec2d10d0254", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "ext-json": "*", + "jetbrains/phpstorm-stubs": "^2019.3", + "phpunit/phpunit": "^9", + "psalm/phar": "^5.15" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Revolt\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Aaron Piotrowski", + "email": "aaron@trowski.com" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "ceesjank@gmail.com" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + }, + { + "name": "Niklas Keller", + "email": "me@kelunik.com" + } + ], + "description": "Rock-solid event loop for concurrent PHP applications.", + "keywords": [ + "async", + "asynchronous", + "concurrency", + "event", + "event-loop", + "non-blocking", + "scheduler" + ], + "support": { + "issues": "https://github.com/revoltphp/event-loop/issues", + "source": "https://github.com/revoltphp/event-loop/tree/v1.0.6" + }, + "time": "2023-11-30T05:34:44+00:00" + }, + { + "name": "seld/jsonlint", + "version": "1.10.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/jsonlint.git", + "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/jsonlint/zipball/9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "reference": "9bb7db07b5d66d90f6ebf542f09fc67d800e5259", + "shasum": "" + }, + "require": { + "php": "^5.3 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^8.5.13" + }, + "bin": [ + "bin/jsonlint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Seld\\JsonLint\\": "src/Seld/JsonLint/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "JSON Linter", + "keywords": [ + "json", + "linter", + "parser", + "validator" + ], + "support": { + "issues": "https://github.com/Seldaek/jsonlint/issues", + "source": "https://github.com/Seldaek/jsonlint/tree/1.10.2" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/seld/jsonlint", + "type": "tidelift" + } + ], + "time": "2024-02-07T12:57:50+00:00" + }, + { + "name": "seld/phar-utils", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/phar-utils.git", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/phar-utils/zipball/ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "reference": "ea2f4014f163c1be4c601b9b7bd6af81ba8d701c", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\PharUtils\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" + } + ], + "description": "PHAR file format utilities, for when PHP phars you up", + "keywords": [ + "phar" + ], + "support": { + "issues": "https://github.com/Seldaek/phar-utils/issues", + "source": "https://github.com/Seldaek/phar-utils/tree/1.2.1" + }, + "time": "2022-08-31T10:31:18+00:00" + }, + { + "name": "seld/signal-handler", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/signal-handler.git", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/signal-handler/zipball/04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "reference": "04a6112e883ad76c0ada8e4a9f7520bbfdb6bb98", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpstan/phpstan": "^1", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.3", + "phpunit/phpunit": "^7.5.20 || ^8.5.23", + "psr/log": "^1 || ^2 || ^3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.x-dev" + } + }, + "autoload": { + "psr-4": { + "Seld\\Signal\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Simple unix signal handler that silently fails where signals are not supported for easy cross-platform development", + "keywords": [ + "posix", + "sigint", + "signal", + "sigterm", + "unix" + ], + "support": { + "issues": "https://github.com/Seldaek/signal-handler/issues", + "source": "https://github.com/Seldaek/signal-handler/tree/2.0.2" + }, + "time": "2023-09-03T09:24:00+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "a2708a5da5c87d1d0d52937bdeac625df659e11f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/a2708a5da5c87d1d0d52937bdeac625df659e11f", + "reference": "a2708a5da5c87d1d0d52937bdeac625df659e11f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v6.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-29T19:07:53+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", + "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.4-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-05-23T14:45:45+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.6", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "9919b5509ada52cc7f66f9a35c86a4a29955c9d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/9919b5509ada52cc7f66f9a35c86a4a29955c9d3", + "reference": "9919b5509ada52cc7f66f9a35c86a4a29955c9d3", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v6.4.6" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-03-21T19:36:20+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v6.4.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-10-31T17:30:12+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.29.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { - "php": ">=7.4.0" + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Psr\\Container\\": "src/" + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -40,76 +2289,79 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" ], "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, - "time": "2021-11-05T16:47:00+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-01-29T20:11:03+00:00" }, { - "name": "symfony/console", - "version": "v6.4.4", + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.29.0", "source": { "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "0d9e4eb5ad413075624378f474c4167ea202de78" + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0d9e4eb5ad413075624378f474c4167ea202de78", - "reference": "0d9e4eb5ad413075624378f474c4167ea202de78", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" - }, - "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "php": ">=7.1" }, - "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "suggest": { + "ext-intl": "For best performance" }, "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\Console\\": "" + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" }, - "exclude-from-classmap": [ - "/Tests/" + "classmap": [ + "Resources/stubs" ] }, "notification-url": "https://packagist.org/downloads/", @@ -118,24 +2370,26 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Eases the creation of beautiful and testable command line interfaces", + "description": "Symfony polyfill for intl's Normalizer class and related functions", "homepage": "https://symfony.com", "keywords": [ - "cli", - "command-line", - "console", - "terminal" + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.4" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -151,39 +2405,45 @@ "type": "tidelift" } ], - "time": "2024-02-22T20:27:10+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { - "name": "symfony/deprecation-contracts", - "version": "v3.4.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.29.0", "source": { "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/7c3aff79d10325257a001fcf92d991f24fc967cf", - "reference": "7c3aff79d10325257a001fcf92d991f24fc967cf", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "3.4-dev" - }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { "files": [ - "function.php" - ] + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -199,10 +2459,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "A generic function and convention to trigger deprecation notices", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.4.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -218,31 +2485,25 @@ "type": "tidelift" } ], - "time": "2023-05-23T14:45:45+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { - "name": "symfony/polyfill-ctype", + "name": "symfony/polyfill-php73", "version": "v1.29.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", - "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", "shasum": "" }, "require": { "php": ">=7.1" }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -255,8 +2516,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -264,24 +2528,24 @@ ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", "polyfill", - "portable" + "portable", + "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" }, "funding": [ { @@ -300,25 +2564,22 @@ "time": "2024-01-29T20:11:03+00:00" }, { - "name": "symfony/polyfill-intl-grapheme", + "name": "symfony/polyfill-php80", "version": "v1.29.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", - "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { "php": ">=7.1" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -331,14 +2592,21 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -348,18 +2616,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's grapheme_* functions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "grapheme", - "intl", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -378,25 +2644,22 @@ "time": "2024-01-29T20:11:03+00:00" }, { - "name": "symfony/polyfill-intl-normalizer", + "name": "symfony/polyfill-php81", "version": "v1.29.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", - "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/c565ad1e63f30e7477fc40738343c62b40bc672d", + "reference": "c565ad1e63f30e7477fc40738343c62b40bc672d", "shasum": "" }, "require": { "php": ">=7.1" }, - "suggest": { - "ext-intl": "For best performance" - }, "type": "library", "extra": { "thanks": { @@ -409,7 +2672,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + "Symfony\\Polyfill\\Php81\\": "" }, "classmap": [ "Resources/stubs" @@ -429,18 +2692,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "intl", - "normalizer", "polyfill", "portable", "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.29.0" }, "funding": [ { @@ -459,42 +2720,30 @@ "time": "2024-01-29T20:11:03+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.29.0", + "name": "symfony/process", + "version": "v6.4.4", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" + "url": "https://github.com/symfony/process.git", + "reference": "710e27879e9be3395de2b98da3f52a946039f297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", - "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "url": "https://api.github.com/repos/symfony/process/zipball/710e27879e9be3395de2b98da3f52a946039f297", + "reference": "710e27879e9be3395de2b98da3f52a946039f297", "shasum": "" }, "require": { - "php": ">=7.1" - }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" + "php": ">=8.1" }, "type": "library", - "extra": { - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -502,25 +2751,18 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" + "source": "https://github.com/symfony/process/tree/v6.4.4" }, "funding": [ { @@ -536,20 +2778,20 @@ "type": "tidelift" } ], - "time": "2024-01-29T20:11:03+00:00" + "time": "2024-02-20T12:31:00+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.4.1", + "version": "v3.4.2", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0" + "reference": "11bbf19a0fb7b36345861e85c5768844c552906e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/fe07cbc8d837f60caf7018068e350cc5163681a0", - "reference": "fe07cbc8d837f60caf7018068e350cc5163681a0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/11bbf19a0fb7b36345861e85c5768844c552906e", + "reference": "11bbf19a0fb7b36345861e85c5768844c552906e", "shasum": "" }, "require": { @@ -602,7 +2844,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.4.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.4.2" }, "funding": [ { @@ -618,7 +2860,7 @@ "type": "tidelift" } ], - "time": "2023-12-26T14:02:43+00:00" + "time": "2023-12-19T21:51:00+00:00" }, { "name": "symfony/string", @@ -705,21 +2947,79 @@ } ], "time": "2024-02-01T13:16:41+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.11.0", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", + "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "php": "^7.2 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<4.6.1 || 4.6.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.13" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, + "time": "2022-06-03T18:03:27+00:00" } ], "packages-dev": [ { "name": "amphp/amp", - "version": "v2.6.2", + "version": "v2.6.4", "source": { "type": "git", "url": "https://github.com/amphp/amp.git", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb" + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/amp/zipball/9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", - "reference": "9d5100cebffa729aaffecd3ad25dc5aeea4f13bb", + "url": "https://api.github.com/repos/amphp/amp/zipball/ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", + "reference": "ded3d9be08f526089eb7ee8d9f16a9768f9dec2d", "shasum": "" }, "require": { @@ -731,8 +3031,8 @@ "ext-json": "*", "jetbrains/phpstorm-stubs": "^2019.3", "phpunit/phpunit": "^7 | ^8 | ^9", - "psalm/phar": "^3.11@dev", - "react/promise": "^2" + "react/promise": "^2", + "vimeo/psalm": "^3.12" }, "type": "library", "extra": { @@ -787,7 +3087,7 @@ "support": { "irc": "irc://irc.freenode.org/amphp", "issues": "https://github.com/amphp/amp/issues", - "source": "https://github.com/amphp/amp/tree/v2.6.2" + "source": "https://github.com/amphp/amp/tree/v2.6.4" }, "funding": [ { @@ -795,20 +3095,20 @@ "type": "github" } ], - "time": "2022-02-20T17:52:18+00:00" + "time": "2024-03-21T18:52:26+00:00" }, { "name": "amphp/byte-stream", - "version": "v1.8.1", + "version": "v1.8.2", "source": { "type": "git", "url": "https://github.com/amphp/byte-stream.git", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd" + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/amphp/byte-stream/zipball/acbd8002b3536485c997c4e019206b3f10ca15bd", - "reference": "acbd8002b3536485c997c4e019206b3f10ca15bd", + "url": "https://api.github.com/repos/amphp/byte-stream/zipball/4f0e968ba3798a423730f567b1b50d3441c16ddc", + "reference": "4f0e968ba3798a423730f567b1b50d3441c16ddc", "shasum": "" }, "require": { @@ -824,11 +3124,6 @@ "psalm/phar": "^3.11.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, "autoload": { "files": [ "lib/functions.php" @@ -847,213 +3142,68 @@ "email": "aaron@trowski.com" }, { - "name": "Niklas Keller", - "email": "me@kelunik.com" - } - ], - "description": "A stream abstraction to make working with non-blocking I/O simple.", - "homepage": "http://amphp.org/byte-stream", - "keywords": [ - "amp", - "amphp", - "async", - "io", - "non-blocking", - "stream" - ], - "support": { - "irc": "irc://irc.freenode.org/amphp", - "issues": "https://github.com/amphp/byte-stream/issues", - "source": "https://github.com/amphp/byte-stream/tree/v1.8.1" - }, - "funding": [ - { - "url": "https://github.com/amphp", - "type": "github" - } - ], - "time": "2021-03-30T17:13:30+00:00" - }, - { - "name": "composer/pcre", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.1" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-10-11T07:11:09+00:00" - }, - { - "name": "composer/semver", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", - "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.4", - "symfony/phpunit-bridge": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" + "name": "Niklas Keller", + "email": "me@kelunik.com" } ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", + "description": "A stream abstraction to make working with non-blocking I/O simple.", + "homepage": "https://amphp.org/byte-stream", "keywords": [ - "semantic", - "semver", - "validation", - "versioning" + "amp", + "amphp", + "async", + "io", + "non-blocking", + "stream" ], "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.0" + "issues": "https://github.com/amphp/byte-stream/issues", + "source": "https://github.com/amphp/byte-stream/tree/v1.8.2" }, "funding": [ { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", + "url": "https://github.com/amphp", "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" } ], - "time": "2023-08-31T09:50:34+00:00" + "time": "2024-04-13T18:00:56+00:00" }, { - "name": "composer/xdebug-handler", - "version": "3.0.3", + "name": "composer/package-versions-deprecated", + "version": "1.11.99.5", "source": { "type": "git", - "url": "https://github.com/composer/xdebug-handler.git", - "reference": "ced299686f41dce890debac69273b47ffe98a40c" + "url": "https://github.com/composer/package-versions-deprecated.git", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/ced299686f41dce890debac69273b47ffe98a40c", - "reference": "ced299686f41dce890debac69273b47ffe98a40c", + "url": "https://api.github.com/repos/composer/package-versions-deprecated/zipball/b4f54f74ef3453349c24a845d22392cd31e65f1d", + "reference": "b4f54f74ef3453349c24a845d22392cd31e65f1d", "shasum": "" }, "require": { - "composer/pcre": "^1 || ^2 || ^3", - "php": "^7.2.5 || ^8.0", - "psr/log": "^1 || ^2 || ^3" + "composer-plugin-api": "^1.1.0 || ^2.0", + "php": "^7 || ^8" + }, + "replace": { + "ocramius/package-versions": "1.11.99" }, "require-dev": { - "phpstan/phpstan": "^1.0", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^6.0" + "composer/composer": "^1.9.3 || ^2.0@dev", + "ext-zip": "^1.13", + "phpunit/phpunit": "^6.5 || ^7" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "1.x-dev" + } }, - "type": "library", "autoload": { "psr-4": { - "Composer\\XdebugHandler\\": "src" + "PackageVersions\\": "src/PackageVersions" } }, "notification-url": "https://packagist.org/downloads/", @@ -1062,19 +3212,18 @@ ], "authors": [ { - "name": "John Stevenson", - "email": "john-stevenson@blueyonder.co.uk" + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be" } ], - "description": "Restarts a process without Xdebug.", - "keywords": [ - "Xdebug", - "performance" - ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", "support": { - "irc": "irc://irc.freenode.org/composer", - "issues": "https://github.com/composer/xdebug-handler/issues", - "source": "https://github.com/composer/xdebug-handler/tree/3.0.3" + "issues": "https://github.com/composer/package-versions-deprecated/issues", + "source": "https://github.com/composer/package-versions-deprecated/tree/1.11.99.5" }, "funding": [ { @@ -1090,7 +3239,7 @@ "type": "tidelift" } ], - "time": "2022-02-25T21:32:43+00:00" + "time": "2022-01-17T14:14:24+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -1584,21 +3733,21 @@ }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v4.19.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4e1b88d21c69391150ace211e9eaf05810858d0b", + "reference": "4e1b88d21c69391150ace211e9eaf05810858d0b", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.1" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", @@ -1634,26 +3783,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.19.1" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-03-17T08:10:35+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -1694,9 +3844,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -1749,6 +3905,60 @@ }, "time": "2022-02-21T01:04:05+00:00" }, + { + "name": "php-standard-library/psalm-plugin", + "version": "2.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-standard-library/psalm-plugin.git", + "reference": "bf6d560ae498966150bc66a42e02744b0ee242c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-standard-library/psalm-plugin/zipball/bf6d560ae498966150bc66a42e02744b0ee242c5", + "reference": "bf6d560ae498966150bc66a42e02744b0ee242c5", + "shasum": "" + }, + "require": { + "php": "^8.1", + "vimeo/psalm": ">=5.16" + }, + "conflict": { + "azjezz/psl": "<2.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "roave/security-advisories": "dev-master", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "psalm-plugin", + "extra": { + "psalm": { + "pluginClass": "Psl\\Psalm\\Plugin" + } + }, + "autoload": { + "psr-4": { + "Psl\\Psalm\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "azjezz", + "email": "azjezz@protonmail.com" + } + ], + "description": "Psalm plugin for the PHP Standard Library", + "support": { + "issues": "https://github.com/php-standard-library/psalm-plugin/issues", + "source": "https://github.com/php-standard-library/psalm-plugin/tree/2.3.0" + }, + "time": "2023-11-28T12:22:48+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -1804,28 +4014,35 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", + "version": "5.4.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" + "reference": "298d2febfe79d03fe714eb871d5538da55205b1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/298d2febfe79d03fe714eb871d5538da55205b1a", + "reference": "298d2febfe79d03fe714eb871d5538da55205b1a", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.1", "ext-filter": "*", - "php": "^7.2 || ^8.0", + "php": "^7.4 || ^8.0", "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7", "webmozart/assert": "^1.9.1" }, "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" + "mockery/mockery": "~1.3.5", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "vimeo/psalm": "^5.13" }, "type": "library", "extra": { @@ -1849,15 +4066,15 @@ }, { "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" + "email": "opensource@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.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.4.0" }, - "time": "2021-10-19T17:43:47+00:00" + "time": "2024-04-09T21:13:58+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -1919,16 +4136,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.26.0", + "version": "1.28.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227" + "reference": "cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/231e3186624c03d7e7c890ec662b81e6b0405227", - "reference": "231e3186624c03d7e7c890ec662b81e6b0405227", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb", + "reference": "cd06d6b1a1b3c75b0b83f97577869fd85a3cd4fb", "shasum": "" }, "require": { @@ -1960,22 +4177,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.26.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.28.0" }, - "time": "2024-02-23T16:05:55+00:00" + "time": "2024-04-03T18:51:33+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "10.1.11", + "version": "10.1.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "78c3b7625965c2513ee96569a4dbb62601784145" + "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145", - "reference": "78c3b7625965c2513ee96569a4dbb62601784145", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", + "reference": "e3f51450ebffe8e0efdf7346ae966a656f7d5e5b", "shasum": "" }, "require": { @@ -2032,7 +4249,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.14" }, "funding": [ { @@ -2040,7 +4257,7 @@ "type": "github" } ], - "time": "2023-12-21T15:38:30+00:00" + "time": "2024-03-12T15:33:41+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2287,16 +4504,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.11", + "version": "10.5.20", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4" + "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4", - "reference": "0d968f6323deb3dbfeba5bfd4929b9415eb7a9a4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/547d314dc24ec1e177720d45c6263fb226cc2ae3", + "reference": "547d314dc24ec1e177720d45c6263fb226cc2ae3", "shasum": "" }, "require": { @@ -2368,7 +4585,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.11" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.20" }, "funding": [ { @@ -2384,34 +4601,49 @@ "type": "tidelift" } ], - "time": "2024-02-25T14:05:00+00:00" + "time": "2024-04-24T06:32:35+00:00" }, { - "name": "psr/log", - "version": "3.0.0", + "name": "psalm/plugin-phpunit", + "version": "0.18.4", "source": { "type": "git", - "url": "https://github.com/php-fig/log.git", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + "url": "https://github.com/psalm/psalm-plugin-phpunit.git", + "reference": "e4ab3096653d9eb6f6d0ea5f4461898d59ae4dbc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", - "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "url": "https://api.github.com/repos/psalm/psalm-plugin-phpunit/zipball/e4ab3096653d9eb6f6d0ea5f4461898d59ae4dbc", + "reference": "e4ab3096653d9eb6f6d0ea5f4461898d59ae4dbc", "shasum": "" }, "require": { - "php": ">=8.0.0" + "composer/package-versions-deprecated": "^1.10", + "composer/semver": "^1.4 || ^2.0 || ^3.0", + "ext-simplexml": "*", + "php": "^7.1 || ^8.0", + "vimeo/psalm": "dev-master || dev-4.x || ^4.7.1 || ^5@beta || ^5.0" }, - "type": "library", + "conflict": { + "phpunit/phpunit": "<7.5" + }, + "require-dev": { + "codeception/codeception": "^4.0.3", + "php": "^7.3 || ^8.0", + "phpunit/phpunit": "^7.5 || ^8.0 || ^9.0", + "squizlabs/php_codesniffer": "^3.3.1", + "weirdan/codeception-psalm-module": "^0.11.0", + "weirdan/prophecy-shim": "^1.0 || ^2.0" + }, + "type": "psalm-plugin", "extra": { - "branch-alias": { - "dev-master": "3.x-dev" + "psalm": { + "pluginClass": "Psalm\\PhpUnitPlugin\\Plugin" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Psalm\\PhpUnitPlugin\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -2420,34 +4652,29 @@ ], "authors": [ { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" + "name": "Matt Brown", + "email": "github@muglug.com" } ], - "description": "Common interface for logging libraries", - "homepage": "https://github.com/php-fig/log", - "keywords": [ - "log", - "psr", - "psr-3" - ], + "description": "Psalm plugin for PHPUnit", "support": { - "source": "https://github.com/php-fig/log/tree/3.0.0" + "issues": "https://github.com/psalm/psalm-plugin-phpunit/issues", + "source": "https://github.com/psalm/psalm-plugin-phpunit/tree/0.18.4" }, - "time": "2021-07-14T16:46:02+00:00" + "time": "2022-12-03T07:47:07+00:00" }, { "name": "sebastian/cli-parser", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae" + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { @@ -2482,7 +4709,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" }, "funding": [ { @@ -2490,7 +4718,7 @@ "type": "github" } ], - "time": "2023-02-03T06:58:15+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { "name": "sebastian/code-unit", @@ -2740,16 +4968,16 @@ }, { "name": "sebastian/diff", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { @@ -2757,7 +4985,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.0", - "symfony/process": "^4.2 || ^5" + "symfony/process": "^6.4" }, "type": "library", "extra": { @@ -2795,7 +5023,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" }, "funding": [ { @@ -2803,20 +5031,20 @@ "type": "github" } ], - "time": "2023-12-22T10:55:06+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { "name": "sebastian/environment", - "version": "6.0.1", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951" + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/43c751b41d74f96cbbd4e07b7aec9675651e2951", - "reference": "43c751b41d74f96cbbd4e07b7aec9675651e2951", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", + "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", "shasum": "" }, "require": { @@ -2831,7 +5059,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -2859,7 +5087,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" }, "funding": [ { @@ -2867,20 +5095,20 @@ "type": "github" } ], - "time": "2023-04-11T05:39:26+00:00" + "time": "2024-03-23T08:47:14+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.1", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc" + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64f51654862e0f5e318db7e9dcc2292c63cdbddc", - "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", "shasum": "" }, "require": { @@ -2937,7 +5165,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" }, "funding": [ { @@ -2945,20 +5173,20 @@ "type": "github" } ], - "time": "2023-09-24T13:22:09+00:00" + "time": "2024-03-02T07:17:12+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4" + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { @@ -2992,14 +5220,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, "funding": [ { @@ -3007,7 +5235,7 @@ "type": "github" } ], - "time": "2023-07-19T07:19:23+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { "name": "sebastian/lines-of-code", @@ -3353,32 +5581,32 @@ }, { "name": "slevomat/coding-standard", - "version": "8.14.1", + "version": "8.15.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926" + "reference": "7d1d957421618a3803b593ec31ace470177d7817" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/fea1fd6f137cc84f9cba0ae30d549615dbc6a926", - "reference": "fea1fd6f137cc84f9cba0ae30d549615dbc6a926", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/7d1d957421618a3803b593ec31ace470177d7817", + "reference": "7d1d957421618a3803b593ec31ace470177d7817", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php": "^7.2 || ^8.0", "phpstan/phpdoc-parser": "^1.23.1", - "squizlabs/php_codesniffer": "^3.7.1" + "squizlabs/php_codesniffer": "^3.9.0" }, "require-dev": { "phing/phing": "2.17.4", "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.10.37", + "phpstan/phpstan": "1.10.60", "phpstan/phpstan-deprecation-rules": "1.1.4", - "phpstan/phpstan-phpunit": "1.3.14", - "phpstan/phpstan-strict-rules": "1.5.1", - "phpunit/phpunit": "8.5.21|9.6.8|10.3.5" + "phpstan/phpstan-phpunit": "1.3.16", + "phpstan/phpstan-strict-rules": "1.5.2", + "phpunit/phpunit": "8.5.21|9.6.8|10.5.11" }, "type": "phpcodesniffer-standard", "extra": { @@ -3402,7 +5630,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.14.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.15.0" }, "funding": [ { @@ -3414,7 +5642,7 @@ "type": "tidelift" } ], - "time": "2023-10-08T07:28:08+00:00" + "time": "2024-03-09T15:20:58+00:00" }, { "name": "spatie/array-to-xml", @@ -3481,16 +5709,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.9.0", + "version": "3.9.2", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" + "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", - "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/aac1f6f347a5c5ac6bc98ad395007df00990f480", + "reference": "aac1f6f347a5c5ac6bc98ad395007df00990f480", "shasum": "" }, "require": { @@ -3557,83 +5785,20 @@ "type": "open_collective" } ], - "time": "2024-02-16T15:06:51+00:00" - }, - { - "name": "symfony/filesystem", - "version": "v6.4.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", - "reference": "7f3b1755eb49297a0827a7575d5d2b2fd11cc9fb", - "shasum": "" - }, - "require": { - "php": ">=8.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-mbstring": "~1.8" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Filesystem\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides basic utilities for the filesystem", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.3" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-01-23T14:51:35+00:00" + "time": "2024-04-23T20:25:34+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -3662,7 +5827,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -3670,20 +5835,20 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" }, { "name": "vimeo/psalm", - "version": "5.22.2", + "version": "5.23.1", "source": { "type": "git", "url": "https://github.com/vimeo/psalm.git", - "reference": "d768d914152dbbf3486c36398802f74e80cfde48" + "reference": "8471a896ccea3526b26d082f4461eeea467f10a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vimeo/psalm/zipball/d768d914152dbbf3486c36398802f74e80cfde48", - "reference": "d768d914152dbbf3486c36398802f74e80cfde48", + "url": "https://api.github.com/repos/vimeo/psalm/zipball/8471a896ccea3526b26d082f4461eeea467f10a4", + "reference": "8471a896ccea3526b26d082f4461eeea467f10a4", "shasum": "" }, "require": { @@ -3780,74 +5945,19 @@ "issues": "https://github.com/vimeo/psalm/issues", "source": "https://github.com/vimeo/psalm" }, - "time": "2024-02-22T23:39:07+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2024-03-11T20:33:46+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "composer/composer": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "8.1.*||8.2.*||8.3.*" + "php": "8.1.*||8.2.*||8.3.*", + "ext-zip": "*" }, "platform-dev": [], "plugin-api-version": "2.6.0" diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 3ee6c03..b9e5857 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -9,6 +9,7 @@ bin/pie src test/unit + test/integration diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 675d305..2808b46 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,14 +6,17 @@ cacheDirectory=".phpunit.cache" executionOrder="depends,defects" requireCoverageMetadata="true" - beStrictAboutCoverageMetadata="true" beStrictAboutOutputDuringTests="true" + displayDetailsOnSkippedTests="true" failOnRisky="true" failOnWarning="true"> test/unit + + test/integration + diff --git a/psalm.xml.dist b/psalm.xml.dist index aa2ce96..cc9b3b2 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -9,10 +9,23 @@ findUnusedCode="true" > - - + + + + - + + + + + + + + + + + + diff --git a/src/Command/DownloadCommand.php b/src/Command/DownloadCommand.php index 97876df..9611d6a 100644 --- a/src/Command/DownloadCommand.php +++ b/src/Command/DownloadCommand.php @@ -4,10 +4,29 @@ namespace Php\Pie\Command; +use Composer\Package\Version\VersionParser; +use InvalidArgumentException; +use Php\Pie\DependencyResolver\DependencyResolver; +use Php\Pie\Downloading\DownloadAndExtract; +use Php\Pie\Platform\OperatingSystem; +use Php\Pie\Platform\TargetPhp\PhpBinaryPath; +use Php\Pie\Platform\TargetPlatform; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; +use Webmozart\Assert\Assert; + +use function array_key_exists; +use function is_array; +use function is_string; +use function reset; +use function sprintf; +use function strtolower; + +use const PHP_VERSION; #[AsCommand( name: 'download', @@ -15,10 +34,114 @@ )] final class DownloadCommand extends Command { + private const ARG_REQUESTED_PACKAGE_AND_VERSION = 'requested-package-and-version'; + private const OPTION_WITH_PHP_CONFIG = 'with-php-config'; + private const OPTION_WITH_PHP_PATH = 'with-php-path'; + + public function __construct( + private readonly DependencyResolver $dependencyResolver, + private readonly DownloadAndExtract $downloadAndExtract, + ) { + parent::__construct(); + } + + public function configure(): void + { + parent::configure(); + + $this->addArgument( + self::ARG_REQUESTED_PACKAGE_AND_VERSION, + InputArgument::REQUIRED, + 'The extension name and version constraint to use, in the format {ext-name}{?:{?version-constraint}{?@stability}}, for example `xdebug/xdebug:^3.4@alpha`, `xdebug/xdebug:@alpha`, `xdebug/xdebug:^3.4`, etc.', + ); + $this->addOption( + self::OPTION_WITH_PHP_CONFIG, + null, + InputOption::VALUE_OPTIONAL, + 'The path to the `php-config` binary to find the target PHP platform on ' . OperatingSystem::NonWindows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_CONFIG . '=/usr/bin/php-config7.4', + ); + $this->addOption( + self::OPTION_WITH_PHP_PATH, + null, + InputOption::VALUE_OPTIONAL, + 'The path to the `php` binary to use as the target PHP platform on ' . OperatingSystem::Windows->asFriendlyName() . ', e.g. --' . self::OPTION_WITH_PHP_PATH . '=C:\usr\php7.4.33\php.exe', + ); + } + public function execute(InputInterface $input, OutputInterface $output): int { - $output->writeln('to do'); // @todo + $phpBinaryPath = PhpBinaryPath::fromCurrentProcess(); + + /** @var mixed $withPhpConfig */ + $withPhpConfig = $input->getOption(self::OPTION_WITH_PHP_CONFIG); + if (is_string($withPhpConfig) && $withPhpConfig !== '') { + $phpBinaryPath = PhpBinaryPath::fromPhpConfigExecutable($withPhpConfig); + } + + /** @var mixed $withPhpPath */ + $withPhpPath = $input->getOption(self::OPTION_WITH_PHP_PATH); + if (is_string($withPhpPath) && $withPhpPath !== '') { + $phpBinaryPath = PhpBinaryPath::fromPhpBinaryPath($withPhpPath); + } + + $targetPlatform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath); + + $output->writeln(sprintf('You are running PHP %s', PHP_VERSION)); + $output->writeln(sprintf( + 'Target PHP installation: %s %s%s, on %s %s (from %s)', + $phpBinaryPath->version(), + $targetPlatform->threadSafety->asShort(), + strtolower($targetPlatform->windowsCompiler !== null ? ', ' . $targetPlatform->windowsCompiler->name : ''), + $targetPlatform->operatingSystem->asFriendlyName(), + $targetPlatform->architecture->name, + $phpBinaryPath->phpBinaryPath, + )); + + $requestedNameAndVersionPair = $this->requestedNameAndVersionPair($input); + + $package = ($this->dependencyResolver)( + $targetPlatform, + $requestedNameAndVersionPair['name'], + $requestedNameAndVersionPair['version'], + ); + + $output->writeln(sprintf('Found package: %s which provides %s', $package->prettyNameAndVersion(), $package->extensionName->nameWithExtPrefix())); + + $downloadedPackage = ($this->downloadAndExtract)($targetPlatform, $package); + + $output->writeln(sprintf( + 'Extracted %s source to: %s', + $downloadedPackage->package->prettyNameAndVersion(), + $downloadedPackage->extractedSourcePath, + )); return Command::SUCCESS; } + + /** @return array{name: non-empty-string, version: non-empty-string|null} */ + private function requestedNameAndVersionPair(InputInterface $input): array + { + $requestedPackageString = $input->getArgument(self::ARG_REQUESTED_PACKAGE_AND_VERSION); + + if (! is_string($requestedPackageString) || $requestedPackageString === '') { + throw new InvalidArgumentException('No package was requested for installation'); + } + + $nameAndVersionPairs = (new VersionParser()) + ->parseNameVersionPairs([$requestedPackageString]); + $requestedNameAndVersionPair = reset($nameAndVersionPairs); + + if (! is_array($requestedNameAndVersionPair)) { + throw new InvalidArgumentException('Failed to parse the name/version pair'); + } + + if (! array_key_exists('version', $requestedNameAndVersionPair)) { + $requestedNameAndVersionPair['version'] = null; + } + + Assert::stringNotEmpty($requestedNameAndVersionPair['name']); + Assert::nullOrStringNotEmpty($requestedNameAndVersionPair['version']); + + return $requestedNameAndVersionPair; + } } diff --git a/src/Container.php b/src/Container.php new file mode 100644 index 0000000..c8e8c8f --- /dev/null +++ b/src/Container.php @@ -0,0 +1,115 @@ +instance(InputInterface::class, new ArgvInput()); + $container->instance(OutputInterface::class, new ConsoleOutput()); + + $container->singleton(DownloadCommand::class); + + $container->singleton(IOInterface::class, static function (ContainerInterface $container): IOInterface { + return new ConsoleIO( + $container->get(InputInterface::class), + $container->get(OutputInterface::class), + new HelperSet([]), + ); + }); + $container->singleton(Composer::class, static function (ContainerInterface $container): Composer { + $io = $container->get(IOInterface::class); + $composer = (new ComposerFactory())->createComposer( + $io, + [ + 'config' => ['lock' => false], + ], + true, + ); + $io->loadConfiguration($composer->getConfig()); + + return $composer; + }); + + $container->singleton( + DependencyResolver::class, + static function (ContainerInterface $container): DependencyResolver { + $composer = $container->get(Composer::class); + $repositorySet = new RepositorySet(); + $repositorySet->addRepository(new CompositeRepository($composer->getRepositoryManager()->getRepositories())); + + return new ResolveDependencyWithComposer( + $repositorySet, + new ResolveTargetPhpToPlatformRepository(), + ); + }, + ); + $container->bind( + ClientInterface::class, + static function (): ClientInterface { + return new Client([RequestOptions::HTTP_ERRORS => false]); + }, + ); + $container->singleton( + AuthHelper::class, + static function (ContainerInterface $container): AuthHelper { + return new AuthHelper( + $container->get(IOInterface::class), + $container->get(Composer::class)->getConfig(), + ); + }, + ); + $container->alias(DownloadZipWithGuzzle::class, DownloadZip::class); + $container->alias(GithubPackageReleaseAssets::class, PackageReleaseAssets::class); + $container->when(GithubPackageReleaseAssets::class) + ->needs('$githubApiBaseUrl') + ->give('https://api.github.com'); + $container->singleton( + DownloadAndExtract::class, + static function (ContainerInterface $container): DownloadAndExtract { + if (Platform::isWindows()) { + return $container->get(WindowsDownloadAndExtract::class); + } + + return $container->get(UnixDownloadAndExtract::class); + }, + ); + + return $container; + } +} diff --git a/src/DependencyResolver/DependencyResolver.php b/src/DependencyResolver/DependencyResolver.php new file mode 100644 index 0000000..e316091 --- /dev/null +++ b/src/DependencyResolver/DependencyResolver.php @@ -0,0 +1,14 @@ +getPrettyName(), + $completePackage->getPrettyVersion(), + $completePackage->getDistUrl(), + ); + } + + public function prettyNameAndVersion(): string + { + return $this->name . ':' . $this->version; + } +} diff --git a/src/DependencyResolver/ResolveDependencyWithComposer.php b/src/DependencyResolver/ResolveDependencyWithComposer.php new file mode 100644 index 0000000..23c199a --- /dev/null +++ b/src/DependencyResolver/ResolveDependencyWithComposer.php @@ -0,0 +1,53 @@ +repositorySet, + ($this->resolveTargetPhpToPlatformRepository)($targetPlatform->phpBinaryPath), + )) + ->findBestCandidate($packageName, $requestedVersion, $preferredStability, null, $repoSetFlags); + + if (! $package instanceof CompletePackageInterface) { + throw UnableToResolveRequirement::fromRequirement($packageName, $requestedVersion); + } + + $type = $package->getType(); + if (! in_array($type, [Package::TYPE_PHP_MODULE, Package::TYPE_ZEND_EXTENSION])) { + throw UnableToResolveRequirement::toPhpOrZendExtension($package, $packageName, $requestedVersion); + } + + return Package::fromComposerCompletePackage($package); + } +} diff --git a/src/DependencyResolver/UnableToResolveRequirement.php b/src/DependencyResolver/UnableToResolveRequirement.php new file mode 100644 index 0000000..1815eef --- /dev/null +++ b/src/DependencyResolver/UnableToResolveRequirement.php @@ -0,0 +1,32 @@ +getName(), + $requiredPackageName, + $requiredVersion !== null ? sprintf(' for version %s', $requiredVersion) : '', + )); + } +} diff --git a/src/Downloading/AddAuthenticationHeader.php b/src/Downloading/AddAuthenticationHeader.php new file mode 100644 index 0000000..1e54478 --- /dev/null +++ b/src/Downloading/AddAuthenticationHeader.php @@ -0,0 +1,38 @@ +downloadUrl === null) { + throw new RuntimeException(sprintf('The package %s does not have a download URL', $package->name)); + } + + $authHeaders = $authHelper->addAuthenticationHeader([], 'github.com', $package->downloadUrl); + array_walk( + $authHeaders, + static function (string $v) use (&$request): void { + // @todo probably process this better + $headerParts = explode(':', $v); + $request = $request->withHeader(trim($headerParts[0]), trim($headerParts[1])); + }, + ); + + return $request; + } +} diff --git a/src/Downloading/AssertHttp.php b/src/Downloading/AssertHttp.php new file mode 100644 index 0000000..d4639d0 --- /dev/null +++ b/src/Downloading/AssertHttp.php @@ -0,0 +1,28 @@ +getStatusCode(); + if ($actualStatusCode !== $expectedStatusCode) { + throw new InvalidArgumentException(sprintf( + 'Expected HTTP %d response, got %d - response: %s', + $expectedStatusCode, + $actualStatusCode, + $response->getBody()->__toString(), + )); + } + } +} diff --git a/src/Downloading/DownloadAndExtract.php b/src/Downloading/DownloadAndExtract.php new file mode 100644 index 0000000..f2f2091 --- /dev/null +++ b/src/Downloading/DownloadAndExtract.php @@ -0,0 +1,14 @@ +client + ->sendAsync( + $request, + [ + RequestOptions::ALLOW_REDIRECTS => true, + RequestOptions::HTTP_ERRORS => false, + RequestOptions::SYNCHRONOUS => true, + ], + ) + ->wait(); + assert($response instanceof ResponseInterface); + + AssertHttp::responseStatusCode(200, $response); + + $tmpZipFile = $localPath . '/downloaded.zip'; + file_put_contents($tmpZipFile, $response->getBody()->__toString()); + + return $tmpZipFile; + } +} diff --git a/src/Downloading/DownloadedPackage.php b/src/Downloading/DownloadedPackage.php new file mode 100644 index 0000000..0e21461 --- /dev/null +++ b/src/Downloading/DownloadedPackage.php @@ -0,0 +1,26 @@ + $expectedAssetNames */ + public static function forPackage(Package $package, array $expectedAssetNames): self + { + return new self(sprintf( + 'Could not find release asset for %s named one of "%s"', + $package->prettyNameAndVersion(), + implode(', ', $expectedAssetNames), + )); + } + + public static function forPackageWithMissingTag(Package $package): self + { + return new self(sprintf( + 'Could not find release by tag name for %s', + $package->prettyNameAndVersion(), + )); + } + + public static function forMissingWindowsCompiler(TargetPlatform $targetPlatform): self + { + return new self(sprintf( + 'Could not determine Windows Compiler for PHP %s on %s', + $targetPlatform->phpBinaryPath->version(), + $targetPlatform->operatingSystem->name, + )); + } +} diff --git a/src/Downloading/ExtractZip.php b/src/Downloading/ExtractZip.php new file mode 100644 index 0000000..e5f0cec --- /dev/null +++ b/src/Downloading/ExtractZip.php @@ -0,0 +1,47 @@ +performExtractionUsingZipArchive($zipFile, $destination); + } + + private function performExtractionUsingZipArchive(string $zipFile, string $destination): string + { + $zip = new ZipArchive(); + + $openError = $zip->open($zipFile); + if ($openError !== true) { + throw new RuntimeException(sprintf( + 'Could not open ZIP [%s]: %s', + $openError === false ? '(false)' : $openError, + $zipFile, + )); + } + + if (! $zip->extractTo($destination)) { + throw new RuntimeException(sprintf('Could not extract ZIP "%s" to path: %s', $zipFile, $destination)); + } + + // @todo maybe improve this; GH wraps archives in a top level directory based on the repo name + // and commit, but does anyone else? :s + $extractedPath = explode('/', $zip->getNameIndex(0))[0]; + + $zip->close(); + + return $destination . '/' . $extractedPath; + } +} diff --git a/src/Downloading/GithubPackageReleaseAssets.php b/src/Downloading/GithubPackageReleaseAssets.php new file mode 100644 index 0000000..0d9bd70 --- /dev/null +++ b/src/Downloading/GithubPackageReleaseAssets.php @@ -0,0 +1,148 @@ +selectMatchingReleaseAsset( + $targetPlatform, + $package, + $this->getReleaseAssetsForPackage($package), + ); + + return $releaseAsset['browser_download_url']; + } + + /** @return non-empty-list */ + private function expectedWindowsAssetNames(TargetPlatform $targetPlatform, Package $package): array + { + if ($targetPlatform->operatingSystem !== OperatingSystem::Windows || $targetPlatform->windowsCompiler === null) { + throw CouldNotFindReleaseAsset::forMissingWindowsCompiler($targetPlatform); + } + + /** + * During development, we swapped compiler/ts around. It is fairly trivial to support both, so we can check + * both formats pretty easily, just to avoid confusion for package maintainers... + */ + return [ + strtolower(sprintf( + 'php_%s-%s-%s-%s-%s-%s.zip', + $package->extensionName->name(), + $package->version, + $targetPlatform->phpBinaryPath->majorMinorVersion(), + $targetPlatform->threadSafety->asShort(), + strtolower($targetPlatform->windowsCompiler->name), + $targetPlatform->architecture->name, + )), + strtolower(sprintf( + 'php_%s-%s-%s-%s-%s-%s.zip', + $package->extensionName->name(), + $package->version, + $targetPlatform->phpBinaryPath->majorMinorVersion(), + strtolower($targetPlatform->windowsCompiler->name), + $targetPlatform->threadSafety->asShort(), + $targetPlatform->architecture->name, + )), + ]; + } + + /** @link https://github.com/squizlabs/PHP_CodeSniffer/issues/3734 */ + // phpcs:disable Squiz.Commenting.FunctionComment.MissingParamName + /** + * @param list $releaseAssets + * + * @return array{name: non-empty-string, browser_download_url: non-empty-string, ...} + */ + // phpcs:enable + private function selectMatchingReleaseAsset(TargetPlatform $targetPlatform, Package $package, array $releaseAssets): array + { + $expectedAssetNames = $this->expectedWindowsAssetNames($targetPlatform, $package); + + foreach ($releaseAssets as $releaseAsset) { + if (in_array(strtolower($releaseAsset['name']), $expectedAssetNames, true)) { + return $releaseAsset; + } + } + + throw Exception\CouldNotFindReleaseAsset::forPackage($package, $expectedAssetNames); + } + + /** @return list */ + private function getReleaseAssetsForPackage(Package $package): array + { + // @todo confirm prettyName will always match the repo name - it might not + $request = AddAuthenticationHeader::withAuthHeaderFromComposer( + new Request('GET', $this->githubApiBaseUrl . '/repos/' . $package->name . '/releases/tags/' . $package->version), + $package, + $this->authHelper, + ); + + $response = $this->client + ->sendAsync( + $request, + [ + RequestOptions::ALLOW_REDIRECTS => true, + RequestOptions::HTTP_ERRORS => false, + RequestOptions::SYNCHRONOUS => true, + ], + ) + ->wait(); + assert($response instanceof ResponseInterface); + + /** @link https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release-by-tag-name */ + if ($response->getStatusCode() === 404) { + throw Exception\CouldNotFindReleaseAsset::forPackageWithMissingTag($package); + } + + AssertHttp::responseStatusCode(200, $response); + + $releaseAssets = Json\typed( + (string) $response->getBody(), + Type\shape( + [ + 'assets' => Type\vec(Type\shape( + [ + 'name' => Type\non_empty_string(), + 'browser_download_url' => Type\non_empty_string(), + ], + true, + )), + ], + true, + ), + ); + + return $releaseAssets['assets']; + } +} diff --git a/src/Downloading/PackageReleaseAssets.php b/src/Downloading/PackageReleaseAssets.php new file mode 100644 index 0000000..d39902d --- /dev/null +++ b/src/Downloading/PackageReleaseAssets.php @@ -0,0 +1,15 @@ +downloadZip->downloadZipAndReturnLocalPath( + $this->createRequestForUnixDownloadUrl($package), + $localTempPath, + ); + + $extractedPath = $this->extractZip->to($tmpZipFile, $localTempPath); + + return DownloadedPackage::fromPackageAndExtractedPath($package, $extractedPath); + } + + private function createRequestForUnixDownloadUrl(Package $package): RequestInterface + { + if ($package->downloadUrl === null) { + throw new RuntimeException(sprintf('The package %s does not have a download URL', $package->name)); + } + + $request = new Request('GET', $package->downloadUrl); + + return AddAuthenticationHeader::withAuthHeaderFromComposer($request, $package, $this->authHelper); + } +} diff --git a/src/Downloading/WindowsDownloadAndExtract.php b/src/Downloading/WindowsDownloadAndExtract.php new file mode 100644 index 0000000..fd317e9 --- /dev/null +++ b/src/Downloading/WindowsDownloadAndExtract.php @@ -0,0 +1,43 @@ +packageReleaseAssets->findWindowsDownloadUrlForPackage($targetPlatform, $package); + + $localTempPath = Path::vaguelyRandomTempPath(); + + $tmpZipFile = $this->downloadZip->downloadZipAndReturnLocalPath( + AddAuthenticationHeader::withAuthHeaderFromComposer( + new Request('GET', $windowsDownloadUrl), + $package, + $this->authHelper, + ), + $localTempPath, + ); + + $this->extractZip->to($tmpZipFile, $localTempPath); + + return DownloadedPackage::fromPackageAndExtractedPath($package, $localTempPath); + } +} diff --git a/src/ExtensionName.php b/src/ExtensionName.php new file mode 100644 index 0000000..afe876b --- /dev/null +++ b/src/ExtensionName.php @@ -0,0 +1,92 @@ +normalisedExtensionName = $normalisedExtensionName; + } + + public static function determineFromComposerPackage(PackageInterface $package): self + { + $phpExt = $package->getPhpExt(); + + /** @psalm-suppress DocblockTypeContradiction just in case runtime type is not correct */ + if ( + $phpExt === null + || ! array_key_exists('extension-name', $phpExt) + || ! is_string($phpExt['extension-name']) + || $phpExt['extension-name'] === '' + ) { + $packageNameParts = explode('/', $package->getPrettyName()); + Assert::count($packageNameParts, 2, 'Expected a package name like vendor/package for ' . $package->getPrettyName()); + Assert::keyExists($packageNameParts, 1); + + return self::normaliseFromString($packageNameParts[1]); + } + + return self::normaliseFromString($phpExt['extension-name']); + } + + public static function normaliseFromString(string $extensionName): self + { + if (str_starts_with($extensionName, 'ext-')) { + return new self(substr($extensionName, strlen('ext-'))); + } + + return new self($extensionName); + } + + /** @return non-empty-string */ + public function name(): string + { + return $this->normalisedExtensionName; + } + + /** @return non-empty-string */ + public function nameWithExtPrefix(): string + { + return 'ext-' . $this->normalisedExtensionName; + } +} diff --git a/src/Platform/Architecture.php b/src/Platform/Architecture.php new file mode 100644 index 0000000..99bcb92 --- /dev/null +++ b/src/Platform/Architecture.php @@ -0,0 +1,27 @@ + self::x86_64, + 'arm64' => self::arm64, + default => self::x86, + }; + } +} diff --git a/src/Platform/OperatingSystem.php b/src/Platform/OperatingSystem.php new file mode 100644 index 0000000..3b66b81 --- /dev/null +++ b/src/Platform/OperatingSystem.php @@ -0,0 +1,20 @@ + 'Windows', + self::NonWindows => 'Linux/OSX/etc', + }; + } +} diff --git a/src/Platform/TargetPhp/PhpBinaryPath.php b/src/Platform/TargetPhp/PhpBinaryPath.php new file mode 100644 index 0000000..0c28d06 --- /dev/null +++ b/src/Platform/TargetPhp/PhpBinaryPath.php @@ -0,0 +1,186 @@ + + */ + public function extensions(): array + { + $extVersionsRawJson = trim((new Process([ + $this->phpBinaryPath, + '-r', + <<<'PHP' +$exts = get_loaded_extensions(); +$extVersions = array_map( + static function ($extension) { + $extVersion = phpversion($extension); + if ($extVersion === false) { + return '0'; + } + return $extVersion; + }, + $exts +); +echo json_encode(array_combine($exts, $extVersions)); +PHP, + ])) + ->mustRun() + ->getOutput()); + + return Json\typed( + $extVersionsRawJson, + Type\dict( + Type\string(), + Type\string(), + ), + ); + } + + public function operatingSystem(): OperatingSystem + { + $winOrNot = trim((new Process([ + $this->phpBinaryPath, + '-r', + 'echo \\defined(\'PHP_WINDOWS_VERSION_BUILD\') ? \'win\' : \'not\';', + ])) + ->mustRun() + ->getOutput()); + Assert::stringNotEmpty($winOrNot, 'Could not determine PHP version'); + + return $winOrNot === 'win' ? OperatingSystem::Windows : OperatingSystem::NonWindows; + } + + /** @return non-empty-string */ + public function version(): string + { + $phpVersion = trim((new Process([ + $this->phpBinaryPath, + '-r', + 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION . "." . PHP_RELEASE_VERSION;', + ])) + ->mustRun() + ->getOutput()); + Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version'); + + // normalizing the version will throw an exception if it is not a valid version + (new VersionParser())->normalize($phpVersion); + + return $phpVersion; + } + + /** @return non-empty-string */ + public function majorMinorVersion(): string + { + $phpVersion = trim((new Process([ + $this->phpBinaryPath, + '-r', + 'echo PHP_MAJOR_VERSION . "." . PHP_MINOR_VERSION;', + ])) + ->mustRun() + ->getOutput()); + Assert::stringNotEmpty($phpVersion, 'Could not determine PHP version'); + + // normalizing the version will throw an exception if it is not a valid version + (new VersionParser())->normalize($phpVersion); + + return $phpVersion; + } + + public function machineType(): Architecture + { + $phpMachineType = trim((new Process([ + $this->phpBinaryPath, + '-r', + 'echo php_uname("m");', + ])) + ->mustRun() + ->getOutput()); + Assert::stringNotEmpty($phpMachineType, 'Could not determine PHP machine type'); + + return Architecture::parseArchitecture($phpMachineType); + } + + public function phpIntSize(): int + { + $phpIntSize = trim((new Process([ + $this->phpBinaryPath, + '-r', + 'echo PHP_INT_SIZE;', + ])) + ->mustRun() + ->getOutput()); + Assert::stringNotEmpty($phpIntSize, 'Could not fetch PHP_INT_SIZE'); + Assert::same($phpIntSize, (string) (int) $phpIntSize, 'PHP_INT_SIZE was not an integer processed %2$s from %s'); + + return (int) $phpIntSize; + } + + /** @return non-empty-string */ + public function phpinfo(): string + { + $phpInfo = trim((new Process([ + $this->phpBinaryPath, + '-i', + ])) + ->mustRun() + ->getOutput()); + + Assert::stringNotEmpty($phpInfo, sprintf('Could not run phpinfo using %s', $this->phpBinaryPath)); + + return $phpInfo; + } + + public static function fromPhpConfigExecutable(string $phpConfig): self + { + $phpExecutable = trim((new Process([$phpConfig, '--php-binary'])) + ->mustRun() + ->getOutput()); + Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.'); + + return new self($phpExecutable); + } + + /** @param non-empty-string $phpBinary */ + public static function fromPhpBinaryPath(string $phpBinary): self + { + return new self($phpBinary); + } + + public static function fromCurrentProcess(): self + { + $phpExecutable = trim((string) (new PhpExecutableFinder())->find()); + Assert::stringNotEmpty($phpExecutable, 'Could not find path to PHP executable.'); + + return new self($phpExecutable); + } +} diff --git a/src/Platform/TargetPhp/PhpBinaryPathBasedPlatformRepository.php b/src/Platform/TargetPhp/PhpBinaryPathBasedPlatformRepository.php new file mode 100644 index 0000000..b9e9d65 --- /dev/null +++ b/src/Platform/TargetPhp/PhpBinaryPathBasedPlatformRepository.php @@ -0,0 +1,67 @@ +versionParser = new VersionParser(); + $this->packages = []; + + $phpVersion = $phpBinaryPath->version(); + $php = new CompletePackage('php', $this->versionParser->normalize($phpVersion), $phpVersion); + $php->setDescription('The PHP interpreter'); + $this->addPackage($php); + + $extVersions = $phpBinaryPath->extensions(); + + foreach ($extVersions as $extension => $extensionVersion) { + $this->addPackage($this->packageForExtension($extension, $extensionVersion)); + } + + parent::__construct(); + } + + private function packageForExtension(string $name, string $prettyVersion): CompletePackage + { + $extraDescription = ''; + + try { + $version = $this->versionParser->normalize($prettyVersion); + } catch (UnexpectedValueException) { + $extraDescription = ' (actual version: ' . $prettyVersion . ')'; + if (Preg::isMatchStrictGroups('{^(\d+\.\d+\.\d+(?:\.\d+)?)}', $prettyVersion, $match)) { + $prettyVersion = $match[1]; + } else { + $prettyVersion = '0'; + } + + $version = $this->versionParser->normalize($prettyVersion); + } + + $package = new CompletePackage( + 'ext-' . str_replace(' ', '-', strtolower($name)), + $version, + $prettyVersion, + ); + $package->setDescription('The ' . $name . ' PHP extension' . $extraDescription); + $package->setType('php-ext'); + + return $package; + } +} diff --git a/src/Platform/TargetPhp/ResolveTargetPhpToPlatformRepository.php b/src/Platform/TargetPhp/ResolveTargetPhpToPlatformRepository.php new file mode 100644 index 0000000..b1abf1e --- /dev/null +++ b/src/Platform/TargetPhp/ResolveTargetPhpToPlatformRepository.php @@ -0,0 +1,16 @@ +operatingSystem(); + + $phpinfo = $phpBinaryPath->phpinfo(); + + $architecture = $phpBinaryPath->machineType(); + + // If we're not on ARM, a more reliable way of determining 32-bit/64-bit is to use PHP_INT_SIZE + if ($architecture !== Architecture::arm64) { + $architecture = $phpBinaryPath->phpIntSize() === 4 ? Architecture::x86 : Architecture::x86_64; + } + + /** + * Based on xdebug.org wizard, copyright Derick Rethans, used under MIT licence + * + * @link https://github.com/xdebug/xdebug.org/blob/aff649f2c3ca303ad471e6ed9dd29c0db16d3e22/src/XdebugVersion.php#L186-L190 + */ + if ( + preg_match('/Architecture([ =>\t]*)(x[0-9]*)/', $phpinfo, $m) + && array_key_exists(2, $m) + && $m[2] !== '' + ) { + $architecture = Architecture::parseArchitecture($m[2]); + } + + $windowsCompiler = null; + $threadSafety = ThreadSafetyMode::ThreadSafe; + + /** + * Based on xdebug.org wizard, copyright Derick Rethans, used under MIT licence + * + * @link https://github.com/xdebug/xdebug.org/blob/aff649f2c3ca303ad471e6ed9dd29c0db16d3e22/src/XdebugVersion.php#L276-L299 + */ + if (preg_match('/PHP Extension Build([ =>\t]+)(API.*)/', $phpinfo, $m)) { + $parts = explode(',', trim($m[2])); + foreach ($parts as $part) { + switch ($part) { + case 'NTS': + $threadSafety = ThreadSafetyMode::NonThreadSafe; + break; + case 'TS': + $threadSafety = ThreadSafetyMode::ThreadSafe; + break; + case 'VC6': + $windowsCompiler = WindowsCompiler::VC6; + break; + case 'VC8': + $windowsCompiler = WindowsCompiler::VC8; + break; + case 'VC9': + $windowsCompiler = WindowsCompiler::VC9; + break; + case 'VC11': + $windowsCompiler = WindowsCompiler::VC11; + break; + case 'VC14': + $windowsCompiler = WindowsCompiler::VC14; + break; + case 'VC15': + $windowsCompiler = WindowsCompiler::VC15; + break; + case 'VS16': + $windowsCompiler = WindowsCompiler::VS16; + break; + } + } + } + + return new self( + $os, + $phpBinaryPath, + $architecture, + $threadSafety, + $windowsCompiler, + ); + } +} diff --git a/src/Platform/ThreadSafetyMode.php b/src/Platform/ThreadSafetyMode.php new file mode 100644 index 0000000..f5fd923 --- /dev/null +++ b/src/Platform/ThreadSafetyMode.php @@ -0,0 +1,20 @@ + 'ts', + self::NonThreadSafe => 'nts', + }; + } +} diff --git a/src/Platform/WindowsCompiler.php b/src/Platform/WindowsCompiler.php new file mode 100644 index 0000000..607925b --- /dev/null +++ b/src/Platform/WindowsCompiler.php @@ -0,0 +1,17 @@ +commandTester = new CommandTester(Container::factory()->get(DownloadCommand::class)); + } + + /** + * @return array + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function validVersionsList(): array + { + $versionsAndExpected = [ + [self::TEST_PACKAGE, self::TEST_PACKAGE . ':1.0.1'], + [self::TEST_PACKAGE . ':^1.0', self::TEST_PACKAGE . ':1.0.1'], + [self::TEST_PACKAGE . ':1.0.1-alpha.3@alpha', self::TEST_PACKAGE . ':1.0.1-alpha.3'], + [self::TEST_PACKAGE . ':*', self::TEST_PACKAGE . ':1.0.1'], + [self::TEST_PACKAGE . ':~1.0.0@alpha', self::TEST_PACKAGE . ':1.0.1'], + [self::TEST_PACKAGE . ':^1.1.0@alpha', self::TEST_PACKAGE . ':1.1.0-alpha.1'], + [self::TEST_PACKAGE . ':~1.0.0', self::TEST_PACKAGE . ':1.0.1'], + // @todo https://github.com/php/pie/issues/13 - in theory, these could work, on NonWindows at least + // [self::TEST_PACKAGE . ':dev-main', self::TEST_PACKAGE . ':???'], + // [self::TEST_PACKAGE . ':dev-main#769f906413d6d1e12152f6d34134cbcd347ca253', self::TEST_PACKAGE . ':???'], + ]; + + return array_combine( + array_map(static fn ($item) => $item[0], $versionsAndExpected), + $versionsAndExpected, + ); + } + + #[DataProvider('validVersionsList')] + public function testDownloadCommandWillDownloadCompatibleExtension(string $requestedVersion, string $expectedVersion): void + { + if (PHP_VERSION_ID < 80300 || PHP_VERSION_ID >= 80400) { + self::markTestSkipped('This test can only run on PHP 8.3 - you are running ' . PHP_VERSION); + } + + $this->commandTester->execute(['requested-package-and-version' => $requestedVersion]); + + $this->commandTester->assertCommandIsSuccessful(); + + $outputString = $this->commandTester->getDisplay(); + self::assertStringContainsString('Found package: ' . $expectedVersion . ' which provides', $outputString); + self::assertStringContainsString('Extracted ' . $expectedVersion . ' source to', $outputString); + } + + #[DataProvider('validVersionsList')] + public function testDownloadingWithPhpConfig(string $requestedVersion, string $expectedVersion): void + { + // @todo This test makes an assumption you're using `ppa:ondrej/php` to have multiple PHP versions. This allows + // us to test scenarios where you run with PHP 8.1 but want to install to a PHP 8.3 instance, for example. + // However, this test isn't very portable, and won't run in CI, so we could do with improving this later. + $phpConfigPath = '/usr/bin/php-config8.3'; + + if (! file_exists($phpConfigPath) || ! is_executable($phpConfigPath)) { + self::markTestSkipped('This test can only run where "' . $phpConfigPath . '" exists and is executable, to target PHP 8.3'); + } + + $this->commandTester->execute([ + '--with-php-config' => $phpConfigPath, + 'requested-package-and-version' => $requestedVersion, + ]); + + $this->commandTester->assertCommandIsSuccessful(); + + $outputString = $this->commandTester->getDisplay(); + self::assertStringContainsString('Found package: ' . $expectedVersion . ' which provides', $outputString); + self::assertStringContainsString('Extracted ' . $expectedVersion . ' source to', $outputString); + } + + #[DataProvider('validVersionsList')] + public function testDownloadingWithPhpPath(string $requestedVersion, string $expectedVersion): void + { + // @todo This test makes an assumption you're using `ppa:ondrej/php` to have multiple PHP versions. This allows + // us to test scenarios where you run with PHP 8.1 but want to install to a PHP 8.3 instance, for example. + // However, this test isn't very portable, and won't run in CI, so we could do with improving this later. + $phpBinaryPath = '/usr/bin/php8.3'; + + if (! file_exists($phpBinaryPath) || ! is_executable($phpBinaryPath)) { + self::markTestSkipped('This test can only run where "' . $phpBinaryPath . '" exists and is executable, to target PHP 8.3'); + } + + $this->commandTester->execute([ + '--with-php-path' => $phpBinaryPath, + 'requested-package-and-version' => $requestedVersion, + ]); + + $this->commandTester->assertCommandIsSuccessful(); + + $outputString = $this->commandTester->getDisplay(); + self::assertStringContainsString('Found package: ' . $expectedVersion . ' which provides', $outputString); + self::assertStringContainsString('Extracted ' . $expectedVersion . ' source to', $outputString); + } + + public function testDownloadCommandFailsWhenUsingIncompatiblePhpVersion(): void + { + if (PHP_VERSION_ID >= 80200) { + self::markTestSkipped('This test can only run on older than PHP 8.2 - you are running ' . PHP_VERSION); + } + + $this->expectException(UnableToResolveRequirement::class); + // 1.0.0 is only compatible with PHP 8.3.0 + $this->commandTester->execute(['requested-package-and-version' => self::TEST_PACKAGE . ':1.0.0']); + } +} diff --git a/test/unit/Command/DownloadCommandTest.php b/test/unit/Command/DownloadCommandTest.php deleted file mode 100644 index 8b68344..0000000 --- a/test/unit/Command/DownloadCommandTest.php +++ /dev/null @@ -1,28 +0,0 @@ -createMock(InputInterface::class); - $output = $this->createMock(OutputInterface::class); - - $output->expects(self::once()) - ->method('writeln') - ->with('to do'); - - $command = new DownloadCommand(); - self::assertSame(0, $command->execute($input, $output)); - } -} diff --git a/test/unit/DependencyResolver/PackageTest.php b/test/unit/DependencyResolver/PackageTest.php new file mode 100644 index 0000000..1454286 --- /dev/null +++ b/test/unit/DependencyResolver/PackageTest.php @@ -0,0 +1,41 @@ +extensionName->name()); + self::assertSame('vendor/foo', $package->name); + self::assertSame('1.2.3', $package->version); + self::assertSame('vendor/foo:1.2.3', $package->prettyNameAndVersion()); + self::assertNull($package->downloadUrl); + } + + public function testFromComposerCompletePackageWithExtensionName(): void + { + $composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3'); + $composerCompletePackage->setPhpExt(['extension-name' => 'ext-something_else']); + + $package = Package::fromComposerCompletePackage($composerCompletePackage); + + self::assertSame('something_else', $package->extensionName->name()); + self::assertSame('vendor/foo', $package->name); + self::assertSame('1.2.3', $package->version); + self::assertSame('vendor/foo:1.2.3', $package->prettyNameAndVersion()); + self::assertNull($package->downloadUrl); + } +} diff --git a/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php b/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php new file mode 100644 index 0000000..604911a --- /dev/null +++ b/test/unit/DependencyResolver/ResolveDependencyWithComposerTest.php @@ -0,0 +1,105 @@ +repositorySet = new RepositorySet(); + $this->repositorySet->addRepository(new CompositeRepository(RepositoryFactory::defaultReposWithDefaultManager(new NullIO()))); + + $this->resolveTargetPhpToPlatformRepository = new ResolveTargetPhpToPlatformRepository(); + } + + public function testPackageThatCanBeResolved(): void + { + $phpBinaryPath = $this->createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::any()) + ->method('version') + ->willReturn('8.3.0'); + + $targetPlatform = new TargetPlatform( + OperatingSystem::NonWindows, + $phpBinaryPath, + Architecture::x86_64, + ThreadSafetyMode::ThreadSafe, + null, + ); + + $package = (new ResolveDependencyWithComposer( + $this->repositorySet, + $this->resolveTargetPhpToPlatformRepository, + ))($targetPlatform, 'asgrim/example-pie-extension', '^1.0'); + + self::assertSame('asgrim/example-pie-extension', $package->name); + self::assertStringStartsWith('1.', $package->version); + } + + /** + * @return array, 1: string, 2: string}> + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function unresolvableDependencies(): array + { + return [ + 'phpVersionTooOld' => [['php' => '8.1.0'], 'asgrim/example-pie-extension', '1.0.0'], + 'phpVersionTooNew' => [['php' => '8.4.0'], 'asgrim/example-pie-extension', '1.0.0'], + 'notAPhpExtension' => [['php' => '8.3.0'], 'ramsey/uuid', '^4.7'], + ]; + } + + /** @param array $platformOverrides */ + #[DataProvider('unresolvableDependencies')] + public function testPackageThatCannotBeResolvedThrowsException(array $platformOverrides, string $package, string $version): void + { + $phpBinaryPath = $this->createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::once()) + ->method('version') + ->willReturn($platformOverrides['php']); + + $targetPlatform = new TargetPlatform( + OperatingSystem::NonWindows, + $phpBinaryPath, + Architecture::x86_64, + ThreadSafetyMode::ThreadSafe, + null, + ); + + $this->expectException(UnableToResolveRequirement::class); + + (new ResolveDependencyWithComposer( + $this->repositorySet, + $this->resolveTargetPhpToPlatformRepository, + ))( + $targetPlatform, + $package, + $version, + ); + } +} diff --git a/test/unit/DependencyResolver/UnableToResolveRequirementTest.php b/test/unit/DependencyResolver/UnableToResolveRequirementTest.php new file mode 100644 index 0000000..e79b3ae --- /dev/null +++ b/test/unit/DependencyResolver/UnableToResolveRequirementTest.php @@ -0,0 +1,48 @@ +createMock(PackageInterface::class); + $package->method('getName')->willReturn('baz/bat'); + + $exception = UnableToResolveRequirement::toPhpOrZendExtension($package, 'foo/bar', '^1.2'); + + self::assertSame('Package baz/bat was not of type php-ext or php-ext-zend (requested foo/bar for version ^1.2).', $exception->getMessage()); + } + + public function testToPhpOrZendExtensionWithoutVersion(): void + { + $package = $this->createMock(PackageInterface::class); + $package->method('getName')->willReturn('baz/bat'); + + $exception = UnableToResolveRequirement::toPhpOrZendExtension($package, 'foo/bar', null); + + self::assertSame('Package baz/bat was not of type php-ext or php-ext-zend (requested foo/bar).', $exception->getMessage()); + } + + public function testFromRequirementWithVersion(): void + { + $exception = UnableToResolveRequirement::fromRequirement('foo/bar', '^1.2'); + + self::assertSame('Unable to find an installable package foo/bar for version ^1.2.', $exception->getMessage()); + } + + public function testFromRequirementWithoutVersion(): void + { + $exception = UnableToResolveRequirement::fromRequirement('foo/bar', null); + + self::assertSame('Unable to find an installable package foo/bar.', $exception->getMessage()); + } +} diff --git a/test/unit/Downloading/AddAuthenticationHeaderTest.php b/test/unit/Downloading/AddAuthenticationHeaderTest.php new file mode 100644 index 0000000..854773c --- /dev/null +++ b/test/unit/Downloading/AddAuthenticationHeaderTest.php @@ -0,0 +1,57 @@ +createMock(AuthHelper::class); + $authHelper->expects(self::once()) + ->method('addAuthenticationHeader') + ->with([], 'github.com', $downloadUrl) + ->willReturn(['Authorization: whatever ABC123']); + + $requestWithAuthHeader = (new AddAuthenticationHeader())->withAuthHeaderFromComposer( + $request, + new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', $downloadUrl), + $authHelper, + ); + + self::assertSame('whatever ABC123', $requestWithAuthHeader->getHeaderLine('Authorization')); + } + + public function testExceptionIsThrownWhenPackageDoesNotHaveDownloadUrl(): void + { + $downloadUrl = 'http://test-uri/' . uniqid('path', true); + + $request = new Request('GET', $downloadUrl); + + $authHelper = $this->createMock(AuthHelper::class); + + $addAuthenticationHeader = new AddAuthenticationHeader(); + $package = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', null); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The package foo/bar does not have a download URL'); + $addAuthenticationHeader->withAuthHeaderFromComposer($request, $package, $authHelper); + } +} diff --git a/test/unit/Downloading/AssertHttpTest.php b/test/unit/Downloading/AssertHttpTest.php new file mode 100644 index 0000000..7568b51 --- /dev/null +++ b/test/unit/Downloading/AssertHttpTest.php @@ -0,0 +1,24 @@ +expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Expected HTTP 201 response, got 404 - response: some body content'); + AssertHttp::responseStatusCode(201, $response); + } +} diff --git a/test/unit/Downloading/DownloadZipWithGuzzleTest.php b/test/unit/Downloading/DownloadZipWithGuzzleTest.php new file mode 100644 index 0000000..ac58dcb --- /dev/null +++ b/test/unit/Downloading/DownloadZipWithGuzzleTest.php @@ -0,0 +1,54 @@ + 'application/octet-stream', + 'Content-length' => (string) (strlen($fakeZipContent)), + ], + $fakeZipContent, + ), + ]); + + $guzzleMockClient = new Client(['handler' => HandlerStack::create($mockHandler)]); + + $localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_test_', true); + mkdir($localPath, 0777, true); + $downloadedZipFile = (new DownloadZipWithGuzzle($guzzleMockClient)) + ->downloadZipAndReturnLocalPath( + new Request('GET', 'http://test-uri/'), + $localPath, + ); + + self::assertSame($fakeZipContent, file_get_contents($downloadedZipFile)); + } +} diff --git a/test/unit/Downloading/DownloadedPackageTest.php b/test/unit/Downloading/DownloadedPackageTest.php new file mode 100644 index 0000000..5ead146 --- /dev/null +++ b/test/unit/Downloading/DownloadedPackageTest.php @@ -0,0 +1,29 @@ +extractedSourcePath); + self::assertSame($package, $downloadedPackage->package); + } +} diff --git a/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php new file mode 100644 index 0000000..b03a335 --- /dev/null +++ b/test/unit/Downloading/Exception/CouldNotFindReleaseAssetTest.php @@ -0,0 +1,52 @@ +getMessage()); + } + + public function testForPackageWithMissingTag(): void + { + $package = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', null); + + $exception = CouldNotFindReleaseAsset::forPackageWithMissingTag($package); + + self::assertSame('Could not find release by tag name for foo/bar:1.2.3', $exception->getMessage()); + } + + public function testForMissingWindowsCompiler(): void + { + $phpBinary = PhpBinaryPath::fromCurrentProcess(); + $exception = CouldNotFindReleaseAsset::forMissingWindowsCompiler(new TargetPlatform( + OperatingSystem::NonWindows, + $phpBinary, + Architecture::x86, + ThreadSafetyMode::NonThreadSafe, + null, + )); + + self::assertSame('Could not determine Windows Compiler for PHP ' . $phpBinary->version() . ' on NonWindows', $exception->getMessage()); + } +} diff --git a/test/unit/Downloading/ExtractZipTest.php b/test/unit/Downloading/ExtractZipTest.php new file mode 100644 index 0000000..cbd8a3a --- /dev/null +++ b/test/unit/Downloading/ExtractZipTest.php @@ -0,0 +1,47 @@ +to(__DIR__ . '/../../assets/test-zip.zip', $localPath); + + // The test-zip.zip should contain a deterministic file content for this: + self::assertSame('Hello there! Test UUID b925c59b-3e6f-4e45-8029-19431df18de4', file_get_contents($extractedPath . DIRECTORY_SEPARATOR . 'test-file.txt')); + } + + public function testFailureToExtractZipWithInvalidZip(): void + { + $localPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . uniqid('pie_test_DO_NOT_EXIST_', true); + + $extr = new ExtractZip(); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage(sprintf('Could not open ZIP [%d]: %s', ZipArchive::ER_NOZIP, __FILE__)); + $extr->to(__FILE__, $localPath); + } +} diff --git a/test/unit/Downloading/GithubPackageReleaseAssetsTest.php b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php new file mode 100644 index 0000000..7c7efd4 --- /dev/null +++ b/test/unit/Downloading/GithubPackageReleaseAssetsTest.php @@ -0,0 +1,152 @@ +createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::any()) + ->method('majorMinorVersion') + ->willReturn('8.3'); + + $targetPlatform = new TargetPlatform( + OperatingSystem::Windows, + $phpBinaryPath, + Architecture::x86, + ThreadSafetyMode::ThreadSafe, + WindowsCompiler::VC14, + ); + + $authHelper = $this->createMock(AuthHelper::class); + + $mockHandler = new MockHandler([ + new Response( + 200, + [], + json_encode([ + 'assets' => [ + [ + 'name' => 'php_foo-1.2.3-8.3-vc14-nts-x86.zip', + 'browser_download_url' => 'wrong_download_url', + ], + [ + 'name' => 'php_foo-1.2.3-8.3-vc14-ts-x86.zip', + 'browser_download_url' => 'actual_download_url', + ], + ], + ]), + ), + ]); + + $guzzleMockClient = new Client(['handler' => HandlerStack::create($mockHandler)]); + + $package = new Package(ExtensionName::normaliseFromString('foo'), 'asgrim/example-pie-extension', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + + $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation'); + + self::assertSame('actual_download_url', $releaseAssets->findWindowsDownloadUrlForPackage($targetPlatform, $package)); + } + + public function testUrlIsReturnedWhenFindingWindowsDownloadUrlWithCompilerAndThreadSafetySwapped(): void + { + $phpBinaryPath = $this->createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::any()) + ->method('majorMinorVersion') + ->willReturn('8.3'); + + $targetPlatform = new TargetPlatform( + OperatingSystem::Windows, + $phpBinaryPath, + Architecture::x86, + ThreadSafetyMode::ThreadSafe, + WindowsCompiler::VC14, + ); + + $authHelper = $this->createMock(AuthHelper::class); + + $mockHandler = new MockHandler([ + new Response( + 200, + [], + json_encode([ + 'assets' => [ + [ + 'name' => 'php_foo-1.2.3-8.3-nts-vc14-x86.zip', + 'browser_download_url' => 'wrong_download_url', + ], + [ + 'name' => 'php_foo-1.2.3-8.3-ts-vc14-x86.zip', + 'browser_download_url' => 'actual_download_url', + ], + ], + ]), + ), + ]); + + $guzzleMockClient = new Client(['handler' => HandlerStack::create($mockHandler)]); + + $package = new Package(ExtensionName::normaliseFromString('foo'), 'asgrim/example-pie-extension', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + + $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation'); + + self::assertSame('actual_download_url', $releaseAssets->findWindowsDownloadUrlForPackage($targetPlatform, $package)); + } + + public function testFindWindowsDownloadUrlForPackageThrowsExceptionWhenAssetNotFound(): void + { + $targetPlatform = new TargetPlatform( + OperatingSystem::Windows, + PhpBinaryPath::fromCurrentProcess(), + Architecture::x86, + ThreadSafetyMode::ThreadSafe, + WindowsCompiler::VC14, + ); + + $authHelper = $this->createMock(AuthHelper::class); + + $mockHandler = new MockHandler([ + new Response( + 200, + [], + json_encode([ + 'assets' => [], + ]), + ), + ]); + + $guzzleMockClient = new Client(['handler' => HandlerStack::create($mockHandler)]); + + $package = new Package(ExtensionName::normaliseFromString('foo'), 'asgrim/example-pie-extension', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + + $releaseAssets = new GithubPackageReleaseAssets($authHelper, $guzzleMockClient, 'https://test-github-api-base-url.thephp.foundation'); + + $this->expectException(CouldNotFindReleaseAsset::class); + $releaseAssets->findWindowsDownloadUrlForPackage($targetPlatform, $package); + } +} diff --git a/test/unit/Downloading/PathTest.php b/test/unit/Downloading/PathTest.php new file mode 100644 index 0000000..8797f86 --- /dev/null +++ b/test/unit/Downloading/PathTest.php @@ -0,0 +1,21 @@ +createMock(DownloadZip::class); + $extractZip = $this->createMock(ExtractZip::class); + $authHelper = $this->createMock(AuthHelper::class); + $unixDownloadAndExtract = new UnixDownloadAndExtract($downloadZip, $extractZip, $authHelper); + + $tmpZipFile = uniqid('tmpZipFile', true); + $extractedPath = uniqid('extractedPath', true); + + $downloadZip->expects(self::once()) + ->method('downloadZipAndReturnLocalPath') + ->with( + self::isInstanceOf(RequestInterface::class), + self::isType('string'), + ) + ->willReturn($tmpZipFile); + + $extractZip->expects(self::once()) + ->method('to') + ->with( + $tmpZipFile, + self::isType('string'), + ) + ->willReturn($extractedPath); + + $downloadUrl = 'https://test-uri/' . uniqid('downloadUrl', true); + $requestedPackage = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', $downloadUrl); + + $downloadedPackage = $unixDownloadAndExtract->__invoke($targetPlatform, $requestedPackage); + + self::assertSame($requestedPackage, $downloadedPackage->package); + self::assertSame($extractedPath, $downloadedPackage->extractedSourcePath); + } +} diff --git a/test/unit/Downloading/WindowsDownloadAndExtractTest.php b/test/unit/Downloading/WindowsDownloadAndExtractTest.php new file mode 100644 index 0000000..448bdd5 --- /dev/null +++ b/test/unit/Downloading/WindowsDownloadAndExtractTest.php @@ -0,0 +1,84 @@ +createMock(DownloadZip::class); + $extractZip = $this->createMock(ExtractZip::class); + $authHelper = $this->createMock(AuthHelper::class); + $packageReleaseAssets = $this->createMock(PackageReleaseAssets::class); + $windowsDownloadAndExtract = new WindowsDownloadAndExtract( + $downloadZip, + $extractZip, + $authHelper, + $packageReleaseAssets, + ); + + $packageReleaseAssets->expects(self::once()) + ->method('findWindowsDownloadUrlForPackage') + ->with($targetPlatform, self::isInstanceOf(Package::class)) + ->willReturn(uniqid('windowsDownloadUrl', true)); + + $tmpZipFile = uniqid('tmpZipFile', true); + $extractedPath = uniqid('extractedPath', true); + + $downloadZip->expects(self::once()) + ->method('downloadZipAndReturnLocalPath') + ->with( + self::isInstanceOf(RequestInterface::class), + self::isType('string'), + ) + ->willReturn($tmpZipFile); + + $extractZip->expects(self::once()) + ->method('to') + ->with( + $tmpZipFile, + self::isType('string'), + ) + ->willReturn($extractedPath); + + $requestedPackage = new Package(ExtensionName::normaliseFromString('foo'), 'foo/bar', '1.2.3', 'https://test-uri/' . uniqid('downloadUrl', true)); + + $downloadedPackage = $windowsDownloadAndExtract->__invoke($targetPlatform, $requestedPackage); + + self::assertSame($requestedPackage, $downloadedPackage->package); + self::assertStringContainsString(sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'pie_downloader_', $downloadedPackage->extractedSourcePath); + } +} diff --git a/test/unit/ExtensionNameTest.php b/test/unit/ExtensionNameTest.php new file mode 100644 index 0000000..da3e657 --- /dev/null +++ b/test/unit/ExtensionNameTest.php @@ -0,0 +1,104 @@ + + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function validExtensionNamesProvider(): array + { + return [ + 'ext-sodium' => ['ext-sodium', 'sodium'], + 'sodium' => ['sodium', 'sodium'], + ]; + } + + #[DataProvider('validExtensionNamesProvider')] + public function testValidExtensionNames(string $givenExtensionName, string $expectedNormalisedName): void + { + $extensionName = ExtensionName::normaliseFromString($givenExtensionName); + + self::assertSame($expectedNormalisedName, $extensionName->name()); + self::assertSame('ext-' . $expectedNormalisedName, $extensionName->nameWithExtPrefix()); + } + + /** + * @return array + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function invalidExtensionNamesProvider(): array + { + $invalidExtensionNames = ['', 'kebab-case', 'money$ext']; + + return array_combine($invalidExtensionNames, array_map(static fn ($extensionName) => [$extensionName], $invalidExtensionNames)); + } + + #[DataProvider('invalidExtensionNamesProvider')] + public function testInvalidExtensionNames(string $invalidExtensionName): void + { + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage(<<setPhpExt(['extension-name' => 'ext-something_else']); + + self::assertSame('something_else', ExtensionName::determineFromComposerPackage($composerCompletePackage)->name()); + } + + public function testFromComposerPackageWithInvalidPhpExtExtensionNameType(): void + { + $composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3'); + /** @psalm-suppress InvalidArgument - the type says otherwise, but in runtime, this might be possible in theory */ + $composerCompletePackage->setPhpExt(['extension-name' => null]); + + self::assertSame('foo', ExtensionName::determineFromComposerPackage($composerCompletePackage)->name()); + } + + public function testFromComposerPackageWithEmptyPhpExtExtensionName(): void + { + $composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3'); + $composerCompletePackage->setPhpExt(['extension-name' => '']); + + self::assertSame('foo', ExtensionName::determineFromComposerPackage($composerCompletePackage)->name()); + } + + public function testFromComposerPackageWithoutPhpExtExtensionName(): void + { + $composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3'); + $composerCompletePackage->setPhpExt(['priority' => 80]); + + self::assertSame('foo', ExtensionName::determineFromComposerPackage($composerCompletePackage)->name()); + } + + public function testFromComposerPackageWithoutPhpExt(): void + { + $composerCompletePackage = new CompletePackage('vendor/foo', '1.2.3.0', '1.2.3'); + + self::assertSame('foo', ExtensionName::determineFromComposerPackage($composerCompletePackage)->name()); + } +} diff --git a/test/unit/Platform/ArchitectureTest.php b/test/unit/Platform/ArchitectureTest.php new file mode 100644 index 0000000..d785786 --- /dev/null +++ b/test/unit/Platform/ArchitectureTest.php @@ -0,0 +1,38 @@ + + * + * @psalm-suppress PossiblyUnusedMethod https://github.com/psalm/psalm-plugin-phpunit/issues/131 + */ + public static function architectureMapProvider(): array + { + return [ + 'x64' => ['x64', Architecture::x86_64], + 'x86_64' => ['x86_64', Architecture::x86_64], + 'AMD64' => ['AMD64', Architecture::x86_64], + 'arm64' => ['arm64', Architecture::arm64], + 'x86' => ['x86', Architecture::x86], + 'something' => ['something', Architecture::x86], + ]; + } + + /** @param non-empty-string $architectureString */ + #[DataProvider('architectureMapProvider')] + public function testParseArchitecture(string $architectureString, Architecture $expectedArchitecture): void + { + self::assertSame($expectedArchitecture, Architecture::parseArchitecture($architectureString)); + } +} diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathBasedPlatformRepositoryTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathBasedPlatformRepositoryTest.php new file mode 100644 index 0000000..83d0e4a --- /dev/null +++ b/test/unit/Platform/TargetPhp/PhpBinaryPathBasedPlatformRepositoryTest.php @@ -0,0 +1,49 @@ +createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::once()) + ->method('version') + ->willReturn('8.1.0'); + $phpBinaryPath->expects(self::once()) + ->method('extensions') + ->willReturn([ + 'json' => '8.1.0-extra', + 'foo' => '8.1.0', + 'without-version' => '0', + 'another' => '1.2.3-alpha.34', + ]); + + $platformRepository = new PhpBinaryPathBasedPlatformRepository($phpBinaryPath); + + self::assertSame( + [ + 'php:8.1.0', + 'ext-json:8.1.0', + 'ext-foo:8.1.0', + 'ext-without-version:0', + 'ext-another:1.2.3-alpha.34', + ], + array_map( + static fn (PackageInterface $package): string => $package->getName() . ':' . $package->getPrettyVersion(), + $platformRepository->getPackages(), + ), + ); + } +} diff --git a/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php new file mode 100644 index 0000000..44ba903 --- /dev/null +++ b/test/unit/Platform/TargetPhp/PhpBinaryPathTest.php @@ -0,0 +1,122 @@ +version(), + ); + } + + public function testFromPhpConfigExecutable(): void + { + $process = (new Process(['which', 'php-config'])); + $exitCode = $process->run(); + $phpConfigExecutable = trim($process->getOutput()); + + if ($exitCode !== 0 || ! file_exists($phpConfigExecutable) || ! is_executable($phpConfigExecutable)) { + self::markTestSkipped('Needs php-config in path to run this test'); + } + + $phpBinary = PhpBinaryPath::fromPhpConfigExecutable($phpConfigExecutable); + + // NOTE: this makes an assumption that the `php-config` in path is the same as the version being executed + // In most cases, this will be the cases (e.g. in CI, running locally), but if you're trying to test this and + // the versions are not matching, that's probably why. + // @todo improve this assertion in future, if it becomes problematic + self::assertSame( + sprintf('%s.%s.%s', PHP_MAJOR_VERSION, PHP_MINOR_VERSION, PHP_RELEASE_VERSION), + $phpBinary->version(), + ); + } + + public function testExtensions(): void + { + $exts = get_loaded_extensions(); + $extVersions = array_map( + static function ($extension) { + $extVersion = phpversion($extension); + if ($extVersion === false) { + return '0'; + } + + return $extVersion; + }, + $exts, + ); + self::assertSame( + array_combine($exts, $extVersions), + PhpBinaryPath::fromCurrentProcess() + ->extensions(), + ); + } + + public function testOperatingSystem(): void + { + self::assertSame( + defined('PHP_WINDOWS_VERSION_BUILD') ? OperatingSystem::Windows : OperatingSystem::NonWindows, + PhpBinaryPath::fromCurrentProcess() + ->operatingSystem(), + ); + } + + public function testMajorMinorVersion(): void + { + self::assertSame( + PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION, + PhpBinaryPath::fromCurrentProcess() + ->majorMinorVersion(), + ); + } + + public function testMachineType(): void + { + $myUnameMachineType = php_uname('m'); + assert($myUnameMachineType !== ''); + self::assertSame( + Architecture::parseArchitecture($myUnameMachineType), + PhpBinaryPath::fromCurrentProcess() + ->machineType(), + ); + } + + public function testPhpIntSize(): void + { + self::assertSame( + PHP_INT_SIZE, + PhpBinaryPath::fromCurrentProcess() + ->phpIntSize(), + ); + } +} diff --git a/test/unit/Platform/TargetPhp/ResolveTargetPhpToPlatformRepositoryTest.php b/test/unit/Platform/TargetPhp/ResolveTargetPhpToPlatformRepositoryTest.php new file mode 100644 index 0000000..49ce656 --- /dev/null +++ b/test/unit/Platform/TargetPhp/ResolveTargetPhpToPlatformRepositoryTest.php @@ -0,0 +1,41 @@ +getPackages(), + static function (BasePackage $package): bool { + return $package->getPrettyName() === 'php'; + }, + ); + + self::assertCount(1, $phpPackages); + assert(count($phpPackages) > 0); + + $phpPackage = $phpPackages[array_key_first($phpPackages)]; + + self::assertSame($php->version(), $phpPackage->getPrettyVersion()); + } +} diff --git a/test/unit/Platform/TargetPlatformTest.php b/test/unit/Platform/TargetPlatformTest.php new file mode 100644 index 0000000..5f0c329 --- /dev/null +++ b/test/unit/Platform/TargetPlatformTest.php @@ -0,0 +1,101 @@ +createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::any()) + ->method('operatingSystem') + ->willReturn(OperatingSystem::Windows); + $phpBinaryPath->expects(self::any()) + ->method('machineType') + ->willReturn(Architecture::x86); + $phpBinaryPath->expects(self::any()) + ->method('phpinfo') + ->willReturn(<<<'TEXT' +phpinfo() +PHP Version => 8.3.6 + +System => Windows NT MYCOMPUTER 10.0 build 19045 (Windows 10) AMD64 +Build Date => Apr 10 2024 14:51:55 +Build System => Microsoft Windows Server 2019 Datacenter [10.0.17763] +Compiler => Visual C++ 2019 +Architecture => x64 +Configure Command => cscript /nologo /e:jscript configure.js "--enable-snapshot-build" "--enable-debug-pack" "--with-pdo-oci=..\..\..\..\instantclient\sdk,shared" "--with-oci8-19=..\..\..\..\instantclient\sdk,shared" "--enable-object-out-dir=../obj/" "--enable-com-dotnet=shared" "--without-analyzer" "--with-pgo" +Server API => Command Line Interface +Virtual Directory Support => enabled +Configuration File (php.ini) Path => +Loaded Configuration File => C:\php-8.3.6\php.ini +Scan this dir for additional .ini files => (none) +Additional .ini files parsed => (none) +PHP API => 20230831 +PHP Extension => 20230831 +Zend Extension => 420230831 +Zend Extension Build => API420230831,TS,VS16 +PHP Extension Build => API20230831,TS,VS16 +TEXT); + + $platform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath); + + self::assertSame(OperatingSystem::Windows, $platform->operatingSystem); + self::assertSame(WindowsCompiler::VS16, $platform->windowsCompiler); + self::assertSame(ThreadSafetyMode::ThreadSafe, $platform->threadSafety); + self::assertSame(Architecture::x86_64, $platform->architecture); + } + + public function testLinuxPlatform(): void + { + $phpBinaryPath = $this->createMock(PhpBinaryPath::class); + $phpBinaryPath->expects(self::any()) + ->method('operatingSystem') + ->willReturn(OperatingSystem::NonWindows); + $phpBinaryPath->expects(self::any()) + ->method('machineType') + ->willReturn(Architecture::x86_64); + $phpBinaryPath->expects(self::any()) + ->method('phpinfo') + ->willReturn(<<<'TEXT' +phpinfo() +PHP Version => 8.3.6 + +System => Linux myhostname 1.2.3 Ubuntu x86_64 +Build Date => Apr 11 2024 20:23:38 +Build System => Linux +Server API => Command Line Interface +Virtual Directory Support => disabled +Configuration File (php.ini) Path => /etc/php/8.3/cli +Loaded Configuration File => /etc/php/8.3/cli/php.ini +Scan this dir for additional .ini files => /etc/php/8.3/cli/conf.d +Additional .ini files parsed => (none) +PHP API => 20230831 +PHP Extension => 20230831 +Zend Extension => 420230831 +Zend Extension Build => API420230831,NTS +PHP Extension Build => API20230831,NTS +Debug Build => no +Thread Safety => disabled +TEXT); + + $platform = TargetPlatform::fromPhpBinaryPath($phpBinaryPath); + + self::assertSame(OperatingSystem::NonWindows, $platform->operatingSystem); + self::assertSame(null, $platform->windowsCompiler); + self::assertSame(ThreadSafetyMode::NonThreadSafe, $platform->threadSafety); + self::assertSame(Architecture::x86_64, $platform->architecture); + } +} diff --git a/test/unit/Platform/ThreadSafetyModeTest.php b/test/unit/Platform/ThreadSafetyModeTest.php new file mode 100644 index 0000000..712db06 --- /dev/null +++ b/test/unit/Platform/ThreadSafetyModeTest.php @@ -0,0 +1,19 @@ +asShort()); + self::assertSame('nts', ThreadSafetyMode::NonThreadSafe->asShort()); + } +}