Ejemplo
guzzlehttp/guzzle 8.0.2: Test code that calls an HTTP API with Guzzle, with no network and no monkeypatching
Muestra verificada para composer guzzlehttp/guzzle 8.0.2: Test code that calls an HTTP API with Guzzle, with no network and no monkeypatching. El contrato se…
sha256:66d105189c76d32ccb87b901f8712d7a4836f4457091fb85af42cf277a9c0f30
Esta red ofrece una sola cosa: una muestra que compila. La ejecutó en un sandbox y guardó el recibo firmado. No califica ni garantiza nada: si el mismo código compila donde estás no es algo que haya medido.
Cuántas claves de firma distintas presentaron un recibo de contrato aprobado. Una es solo el autor; más de una significa que alguien más también lo compiló. Una clave se genera sola y no tiene identidad registrada detrás, así que cuenta claves, no personas.
MIT-0
Evidencia de ejecución
El entorno declarado y las ejecuciones firmadas se muestran por separado, para que veas exactamente qué ejecutó esta muestra y dónde.
- Base de evidencia
- Contrato firmado aprobado
- Recibos de verificación
- 2
- Claves de firma que lo compilaron
- 2
Entorno declarado
php 8 linux x64 php 8 php composer
Entornos de las ejecuciones de verificación
| Entorno | Contrato | Etapas | Ejecución |
|---|---|---|---|
| php 8 · linux alpine/x64 · docker ed25519:a2ec939a4c60e243 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · composer@1 |
2026-08-14 |
| php 8 · linux alpine/x64 · docker ed25519:d91480838ac982c9 | PASS | compile:SKIPPED · contract:PASS · load:PASS · resolve:PASS CONTAINER_RUN · composer@1 |
2026-08-18 |
Caso
HOW- Objetivo
- Test code that calls an HTTP API with Guzzle, with no network and no monkeypatching
- Paquetes
- Símbolos
-
- GuzzleHttp.Handler.MockHandler
- HandlerStack::create
- Middleware::history
- RequestOptions::HTTP_ERRORS
- GuzzleHttp.Exception.RequestException
- GuzzleHttp.Exception.ResponseException
- Entorno
- php 8
- Creado
- 2026-08-14T09:42:37Z
Contrato
- assert a MockHandler queue serves its responses in order while base_uri, the query option and the default headers still apply, and that history holds every exchange while MockHandler::getLastRequest holds only the most recent
- assert the queue never looks at the request, so only the recorded request proves which method and endpoint were called
- assert base_uri is merged by reference resolution: a leading slash on the request path replaces the base path, and a base_uri without a trailing slash loses its last segment
- assert http_errors turns a 500 into a ServerException whose response body is still readable from its current position and whose exception code is the status
- assert getResponse and hasResponse are gone from RequestException so the guzzle 7 idiom is a fatal Error, and getResponse now lives on ResponseException with a non-nullable return type
- assert http_errors set to false returns the 500 response instead of throwing
- assert a queued throwable is thrown as the same object and carries no response side
- assert pushed history records the prepared request with Content-Length but records a 500 as a fulfilled response
- assert unshifted history records the ServerException but its captured request has no Content-Length
- assert a dry queue makes an async call return a rejected promise rather than throwing at the call site, and that waiting on it raises a plain OutOfBoundsException that is not a GuzzleException
- assert a bare HandlerStack built with new drops http_errors so a 500 comes back as a response
Archivos
- composer.json
- composer.lock
- csx.json
- phpunit.xml
- src/Api.php
- test/ApiTest.php
Código fuente
{
"name": "csx/guzzle-mock-handler",
"license": "MIT-0",
"require": {
"guzzlehttp/guzzle": "8.0.2"
},
"require-dev": {
"phpunit/phpunit": "13.3.1"
},
"autoload": {
"psr-4": {
"Csx\\": "src/"
}
},
"config": {
"platform": {
"php": "8.5.0"
}
}
}
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c4b2419149968f2e8eeadf92cc1cae44",
"packages": [
{
"name": "guzzlehttp/guzzle",
"version": "8.0.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "d1cbca76970939a9c2ced55b1e25ea26f34fc773"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/d1cbca76970939a9c2ced55b1e25ea26f34fc773",
"reference": "d1cbca76970939a9c2ced55b1e25ea26f34fc773",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^3.0.1",
"guzzlehttp/psr7": "^3.0",
"php": "^7.4 || ^8.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0",
"symfony/polyfill-php80": "^1.25",
"symfony/polyfill-php82": "^1.27"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"ext-curl": "*",
"guzzle/client-integration-tests": "4.0.1",
"guzzlehttp/test-server": "^1.0",
"php-http/message-factory": "^1.1",
"phpunit/phpunit": "^9.6.34",
"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": {
"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/8.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/guzzle",
"type": "tidelift"
}
],
"time": "2026-08-05T19:50:26+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "3.0.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "64f38b87fa7d371853804161bfc701c9bc2cc00a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/64f38b87fa7d371853804161bfc701c9bc2cc00a",
"reference": "64f38b87fa7d371853804161bfc701c9bc2cc00a",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"phpunit/phpunit": "^9.6.34"
},
"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/3.0.1"
},
"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": "2026-08-05T19:35:04+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "b094ded77ee97a6027ad6cf0e8c7b9f88381814c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/b094ded77ee97a6027ad6cf0e8c7b9f88381814c",
"reference": "b094ded77ee97a6027ad6cf0e8c7b9f88381814c",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"psr/http-factory": "^1.1",
"psr/http-message": "^2.0",
"symfony/polyfill-php80": "^1.25",
"symfony/polyfill-php82": "^1.27"
},
"provide": {
"psr/http-factory-implementation": "1.1",
"psr/http-message-implementation": "2.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.8.2",
"http-interop/http-factory-tests": "1.1.0",
"jshttp/mime-db": "1.54.0.1",
"php-http/psr7-integration-tests": "^1.5.1",
"phpunit/phpunit": "^9.6.34"
},
"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/3.0.0"
},
"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": "2026-07-20T13:48:31+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.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-factory.git",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a",
"shasum": ""
},
"require": {
"php": ">=7.1",
"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": "PSR-17: 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"
},
"time": "2024-04-15T12:06:14+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": "symfony/polyfill-php80",
"version": "v1.37.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
"reference": "dfb55726c3a76ea3b6459fcfda1ec2d80a682411",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.37.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-04-10T16:19:22+00:00"
},
{
"name": "symfony/polyfill-php82",
"version": "v1.38.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php82.git",
"reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php82/zipball/002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b",
"reference": "002dc0cfe5fd4ed6033d48f27d4f19a486c4b04b",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php82\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php82/tree/v1.38.1"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2026-05-26T12:45:58+00:00"
}
],
"packages-dev": [
{
"name": "myclabs/deep-copy",
"version": "1.14.0",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
"reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae",
"reference": "8680aa248f8e07bc8fb43f56f0f5fc77a0c96aae",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"conflict": {
"doctrine/collections": "<1.6.8",
"doctrine/common": "<2.13.3 || >=3 <3.2.2"
},
"require-dev": {
"doctrine/collections": "^1.6.8",
"doctrine/common": "^2.13.3 || ^3.2.2",
"phpspec/prophecy": "^1.10",
"phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
},
"type": "library",
"autoload": {
"files": [
"src/DeepCopy/deep_copy.php"
],
"psr-4": {
"DeepCopy\\": "src/DeepCopy/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Create deep copies (clones) of your objects",
"keywords": [
"clone",
"copy",
"duplicate",
"object",
"object graph"
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
"source": "https://github.com/myclabs/DeepCopy/tree/1.14.0"
},
"funding": [
{
"url": "https://github.com/mnapoli",
"type": "github"
}
],
"time": "2026-08-11T10:17:44+00:00"
},
{
"name": "nikic/php-parser",
"version": "v5.8.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"reference": "044a6a392ff8ad0d61f14370a5fbbd0a0107152f",
"shasum": ""
},
"require": {
"ext-json": "*",
"ext-tokenizer": "*",
"php": ">=7.4"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^9.0"
},
"bin": [
"bin/php-parse"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "5.x-dev"
}
},
"autoload": {
"psr-4": {
"PhpParser\\": "lib/PhpParser"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Nikita Popov"
}
],
"description": "A PHP parser written in PHP",
"keywords": [
"parser",
"php"
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v5.8.0"
},
"time": "2026-07-04T14:30:18+00:00"
},
{
"name": "phar-io/manifest",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
"reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "zip",
"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",
"php": "^7.2 || ^8.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"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.4"
},
"funding": [
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2024-03-03T12:33:53+00:00"
},
{
"name": "phar-io/version",
"version": "3.2.1",
"source": {
"type": "git",
"url": "https://github.com/phar-io/version.git",
"reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
"reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
"shasum": ""
},
"require": {
"php": "^7.2 || ^8.0"
},
"type": "library",
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Arne Blankerts",
"email": "arne@blankerts.de",
"role": "Developer"
},
{
"name": "Sebastian Heuer",
"email": "sebastian@phpeople.de",
"role": "Developer"
},
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "Developer"
}
],
"description": "Library for handling version information and constraints",
"support": {
"issues": "https://github.com/phar-io/version/issues",
"source": "https://github.com/phar-io/version/tree/3.2.1"
},
"time": "2022-02-21T01:04:05+00:00"
},
{
"name": "phpunit/php-code-coverage",
"version": "14.3.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "060a5c93e81afb01e97dbe8930a5d0c9bd46e54e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/060a5c93e81afb01e97dbe8930a5d0c9bd46e54e",
"reference": "060a5c93e81afb01e97dbe8930a5d0c9bd46e54e",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^5.8.0",
"php": ">=8.4",
"phpunit/php-text-template": "^6.0",
"sebastian/complexity": "^6.0",
"sebastian/environment": "^9.3.2",
"sebastian/git-state": "^1.0",
"sebastian/lines-of-code": "^5.0.2",
"sebastian/version": "^7.0",
"theseer/tokenizer": "^2.0.1"
},
"require-dev": {
"phpunit/phpunit": "^13.3"
},
"suggest": {
"ext-pcov": "PHP extension that provides line coverage",
"ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "14.3.x-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
"homepage": "https://github.com/sebastianbergmann/php-code-coverage",
"keywords": [
"coverage",
"testing",
"xunit"
],
"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/14.3.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage",
"type": "tidelift"
}
],
"time": "2026-08-07T07:24:15+00:00"
},
{
"name": "phpunit/php-file-iterator",
"version": "7.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-file-iterator.git",
"reference": "3ccaa29123548190af12fee7af078dcd7f3ddfab"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/3ccaa29123548190af12fee7af078dcd7f3ddfab",
"reference": "3ccaa29123548190af12fee7af078dcd7f3ddfab",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.3.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "7.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "FilterIterator implementation that filters files based on a list of suffixes.",
"homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
"keywords": [
"filesystem",
"iterator"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
"security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
"source": "https://github.com/sebastianbergmann/php-file-iterator/tree/7.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-file-iterator",
"type": "tidelift"
}
],
"time": "2026-08-10T06:43:12+00:00"
},
{
"name": "phpunit/php-invoker",
"version": "7.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-invoker.git",
"reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88",
"reference": "42e5c5cae0c65df12d1b1a3ab52bf3f50f244d88",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"ext-pcntl": "*",
"phpunit/phpunit": "^13.0"
},
"suggest": {
"ext-pcntl": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "7.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Invoke callables with a timeout",
"homepage": "https://github.com/sebastianbergmann/php-invoker/",
"keywords": [
"process"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-invoker/issues",
"security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
"source": "https://github.com/sebastianbergmann/php-invoker/tree/7.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-invoker",
"type": "tidelift"
}
],
"time": "2026-02-06T04:34:47+00:00"
},
{
"name": "phpunit/php-text-template",
"version": "6.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-text-template.git",
"reference": "a47af19f93f76aa3368303d752aa5272ca3299f4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/a47af19f93f76aa3368303d752aa5272ca3299f4",
"reference": "a47af19f93f76aa3368303d752aa5272ca3299f4",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Simple template engine.",
"homepage": "https://github.com/sebastianbergmann/php-text-template/",
"keywords": [
"template"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-text-template/issues",
"security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
"source": "https://github.com/sebastianbergmann/php-text-template/tree/6.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-text-template",
"type": "tidelift"
}
],
"time": "2026-02-06T04:36:37+00:00"
},
{
"name": "phpunit/php-timer",
"version": "9.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-timer.git",
"reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/a0e12065831f6ab0d83120dc61513eb8d9a966f6",
"reference": "a0e12065831f6ab0d83120dc61513eb8d9a966f6",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Utility class for timing",
"homepage": "https://github.com/sebastianbergmann/php-timer/",
"keywords": [
"timer"
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-timer/issues",
"security": "https://github.com/sebastianbergmann/php-timer/security/policy",
"source": "https://github.com/sebastianbergmann/php-timer/tree/9.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/phpunit/php-timer",
"type": "tidelift"
}
],
"time": "2026-02-06T04:37:53+00:00"
},
{
"name": "phpunit/phpunit",
"version": "13.3.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "fc024931d6ad047404e9d86536735923fe63a06b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/fc024931d6ad047404e9d86536735923fe63a06b",
"reference": "fc024931d6ad047404e9d86536735923fe63a06b",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-filter": "*",
"ext-json": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
"ext-xmlwriter": "*",
"myclabs/deep-copy": "^1.14.0",
"phar-io/manifest": "^2.0.4",
"phar-io/version": "^3.2.1",
"php": ">=8.4.1",
"phpunit/php-code-coverage": "^14.3",
"phpunit/php-file-iterator": "^7.0.1",
"phpunit/php-invoker": "^7.0.0",
"phpunit/php-text-template": "^6.0.0",
"phpunit/php-timer": "^9.0.0",
"sebastian/cli-parser": "^5.0.1",
"sebastian/comparator": "^8.4",
"sebastian/diff": "^9.0",
"sebastian/environment": "^9.3.2",
"sebastian/exporter": "^8.2.1",
"sebastian/file-filter": "^1.0",
"sebastian/git-state": "^1.0",
"sebastian/global-state": "^9.0.1",
"sebastian/object-enumerator": "^8.1.0",
"sebastian/recursion-context": "^8.0.1",
"sebastian/type": "^7.0.2",
"sebastian/version": "^7.0.0",
"staabm/side-effects-detector": "^1.0.5"
},
"bin": [
"phpunit"
],
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "13.3-dev"
}
},
"autoload": {
"files": [
"src/Framework/Assert/Functions.php"
],
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "The PHP Unit Testing framework.",
"homepage": "https://phpunit.de/",
"keywords": [
"phpunit",
"testing",
"xunit"
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/13.3.1"
},
"funding": [
{
"url": "https://phpunit.de/sponsoring.html",
"type": "other"
}
],
"time": "2026-08-13T13:14:23+00:00"
},
{
"name": "sebastian/cli-parser",
"version": "5.0.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
"reference": "eeb759ad3146b7096fb59c3195d39e071cd409e3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/eeb759ad3146b7096fb59c3195d39e071cd409e3",
"reference": "eeb759ad3146b7096fb59c3195d39e071cd409e3",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.2.6"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "5.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library for parsing CLI options",
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
"source": "https://github.com/sebastianbergmann/cli-parser/tree/5.0.1"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/cli-parser",
"type": "tidelift"
}
],
"time": "2026-08-01T04:27:14+00:00"
},
{
"name": "sebastian/comparator",
"version": "8.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "3b070e608146cba00fd6fd1f0ffba89e5a8897fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/3b070e608146cba00fd6fd1f0ffba89e5a8897fb",
"reference": "3b070e608146cba00fd6fd1f0ffba89e5a8897fb",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-mbstring": "*",
"php": ">=8.4",
"sebastian/diff": "^9.0",
"sebastian/exporter": "^8.2"
},
"require-dev": {
"phpunit/phpunit": "^13.3"
},
"suggest": {
"ext-bcmath": "For comparing BcMath\\Number objects"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "8.4-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@2bepublished.at"
}
],
"description": "Provides the functionality to compare PHP values for equality",
"homepage": "https://github.com/sebastianbergmann/comparator",
"keywords": [
"comparator",
"compare",
"equality"
],
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/8.4.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/comparator",
"type": "tidelift"
}
],
"time": "2026-08-07T07:23:13+00:00"
},
{
"name": "sebastian/complexity",
"version": "6.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "c5651c795c98093480df79350cb050813fc7a2f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/c5651c795c98093480df79350cb050813fc7a2f3",
"reference": "c5651c795c98093480df79350cb050813fc7a2f3",
"shasum": ""
},
"require": {
"nikic/php-parser": "^5.0",
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "6.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de",
"role": "lead"
}
],
"description": "Library for calculating the complexity of PHP code units",
"homepage": "https://github.com/sebastianbergmann/complexity",
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"security": "https://github.com/sebastianbergmann/complexity/security/policy",
"source": "https://github.com/sebastianbergmann/complexity/tree/6.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/complexity",
"type": "tidelift"
}
],
"time": "2026-02-06T04:41:32+00:00"
},
{
"name": "sebastian/diff",
"version": "9.0.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/a3fb6a298a265ff487a91bbea46e03cd01dbb226",
"reference": "a3fb6a298a265ff487a91bbea46e03cd01dbb226",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.2",
"symfony/process": "^7.4.13"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.0-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Kore Nordmann",
"email": "mail@kore-nordmann.de"
}
],
"description": "Diff implementation",
"homepage": "https://github.com/sebastianbergmann/diff",
"keywords": [
"diff",
"udiff",
"unidiff",
"unified diff"
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"security": "https://github.com/sebastianbergmann/diff/security/policy",
"source": "https://github.com/sebastianbergmann/diff/tree/9.0.0"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/diff",
"type": "tidelift"
}
],
"time": "2026-06-05T03:04:51+00:00"
},
{
"name": "sebastian/environment",
"version": "9.3.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e",
"reference": "6c9e487c9eb706a8d258102a1c0b0a3e53e86c2e",
"shasum": ""
},
"require": {
"php": ">=8.4"
},
"require-dev": {
"phpunit/phpunit": "^13.1.11"
},
"suggest": {
"ext-posix": "*"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "9.3-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
}
],
"description": "Provides functionality to handle HHVM/PHP environments",
"homepage": "https://github.com/sebastianbergmann/environment",
"keywords": [
"Xdebug",
"environment",
"hhvm"
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"security": "https://github.com/sebastianbergmann/environment/security/policy",
"source": "https://github.com/sebastianbergmann/environment/tree/9.3.2"
},
"funding": [
{
"url": "https://github.com/sebastianbergmann",
"type": "github"
},
{
"url": "https://liberapay.com/sebastianbergmann",
"type": "liberapay"
},
{
"url": "https://thanks.dev/u/gh/sebastianbergmann",
"type": "thanks_dev"
},
{
"url": "https://tidelift.com/funding/github/packagist/sebastian/environment",
"type": "tidelift"
}
],
"time": "2026-05-25T13:41:38+00:00"
},
{
"name": "sebastian/exporter",
"version": "8.2.1",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "24a3b69bba4a12ab615fca9d34680c5598d9ab7a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/24a3b69bba4a12ab615fca9d34680c5598d9ab7a",
"reference": "24a3b69bba4a12ab615fca9d34680c5598d9ab7a",
"shasum": ""
},
"require": {
"ext-mbstring": "*",
"php": ">=8.4",
"sebastian/recursion-context": "^8.0.1"
},
"require-dev": {
"phpunit/phpunit": "^13.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "8.2-dev"
}
},
"autoload": {
"classmap": [
"src/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Sebastian Bergmann",
"email": "sebastian@phpunit.de"
},
{
"name": "Jeff Welch",
"email": "whatthejeff@gmail.com"
},
{
"name": "Volker Dusch",
"email": "github@wallbash.com"
},
{
"name": "Adam Harvey",
"email": "aharvey@php.net"
},
{
"name": "Bernhard Schussek",
"email": "bschussek@gmail.com"
}
],
"description": "Provides the functionality to export PHP variables for visualization",
"h
{"case":{"caseId":"case:sha256:79fb467acc0fac4149011bfae6ea40cba7666807b8fe6d09b3ba0268d3a71ee0","constraints":{"runtime":"php"},"contract":["assert a MockHandler queue serves its responses in order while base_uri, the query option and the default headers still apply, and that history holds every exchange while MockHandler::getLastRequest holds only the most recent","assert the queue never looks at the request, so only the recorded request proves which method and endpoint were called","assert base_uri is merged by reference resolution: a leading slash on the request path replaces the base path, and a base_uri without a trailing slash loses its last segment","assert http_errors turns a 500 into a ServerException whose response body is still readable from its current position and whose exception code is the status","assert getResponse and hasResponse are gone from RequestException so the guzzle 7 idiom is a fatal Error, and getResponse now lives on ResponseException with a non-nullable return type","assert http_errors set to false returns the 500 response instead of throwing","assert a queued throwable is thrown as the same object and carries no response side","assert pushed history records the prepared request with Content-Length but records a 500 as a fulfilled response","assert unshifted history records the ServerException but its captured request has no Content-Length","assert a dry queue makes an async call return a rejected promise rather than throwing at the call site, and that waiting on it raises a plain OutOfBoundsException that is not a GuzzleException","assert a bare HandlerStack built with new drops http_errors so a 500 comes back as a response"],"goal":"Test code that calls an HTTP API with Guzzle, with no network and no monkeypatching","kind":"HOW","packages":["pkg:composer/guzzlehttp/guzzle@8.0.2"],"schemaVersion":1,"symbols":["GuzzleHttp.Handler.MockHandler","HandlerStack::create","Middleware::history","RequestOptions::HTTP_ERRORS","GuzzleHttp.Exception.RequestException","GuzzleHttp.Exception.ResponseException"]},"contractCommand":["php","vendor/bin/phpunit","--colors=never"],"environment":{"arch":"x64","ecosystem":"composer","executionContext":"php","language":"php","os":"linux","packageManager":"composer","runtime":"php","runtimeVersion":"8","schemaVersion":1},"license":"MIT-0","packages":["pkg:composer/guzzlehttp/guzzle@8.0.2"],"schemaVersion":1,"symbols":["GuzzleHttp.Handler.MockHandler","HandlerStack::create","Middleware::history","RequestOptions::HTTP_ERRORS","GuzzleHttp.Exception.RequestException","GuzzleHttp.Exception.ResponseException"],"verifierAdapter":"composer@1"}
<?xml version="1.0"?>
<phpunit bootstrap="vendor/autoload.php" colors="false" recordTestRunHistory="false">
<testsuites><testsuite name="csx"><directory>test</directory></testsuite></testsuites>
</phpunit>
<?php
namespace Csx;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;
use GuzzleHttp\RequestOptions;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
/**
* Testing Guzzle 8 with no network, and the four things that are not what the
* mock-transport habit or the Guzzle 7 docs lead you to expect.
*
* MockHandler is a queue, not a router. It never looks at the request: the
* next entry comes back whatever method or URL you called, so a test can pass
* while the code under test hits the wrong endpoint entirely. Proving what was
* sent is a separate job, and there are two tools for it: getLastRequest() on
* the MockHandler keeps only the most recent request, while the History
* middleware keeps every exchange in order — which is why history is worth
* pushing rather than treated as an optional extra. Running the queue dry is a
* test-setup error rather than a transport error: MockHandler throws a plain
* OutOfBoundsException that no GuzzleException catch block will see.
*
* Where history sits in the stack changes what it records, and both positions
* are wrong for something. HandlerStack::create() has already pushed
* http_errors and prepare_body, and resolve() wraps the stack from the end
* inwards, so push() lands nearer the handler than anything create() added:
* - pushed (inner): sees the Content-Length prepare_body added, but records
* a 500 as a fulfilled response with error => null, because http_errors
* converts it to a rejection further out.
* - unshifted (outer): records the ServerException in error, but the
* request it captured has no Content-Length yet.
* Content-Type is on the request in both positions. In Guzzle 8 the json
* option's conditional Content-Type is applied by Client::applyOptions before
* any middleware runs, so unlike Content-Length it does not depend on
* prepare_body — which no longer merges conditional headers at all.
*
* The handler you pass matters just as much. `new HandlerStack($mock)` is a
* bare stack with no middleware, which means http_errors is silently inert: a
* 500 comes back as a response and every error-path test passes for the wrong
* reason. HandlerStack::create($mock) is what you want.
*
* Finally, the Guzzle 7 error-handling idiom is a fatal error on 8.
* getResponse() and hasResponse() are gone from RequestException; getResponse()
* now lives on ResponseException and is non-nullable. ServerException still
* extends RequestException, so `catch (RequestException $e)` still catches a
* 500 — it is the `$e->getResponse()` inside that catch block that dies.
* Catch ResponseException (or BadResponseException) when you want the body.
*/
final class Api
{
/**
* Filled by the History middleware.
*
* @var list<array{request: RequestInterface, response: ?ResponseInterface, error: mixed, options: array}>
*/
public array $history = [];
public readonly Client $client;
public readonly MockHandler $mock;
/**
* @param list<mixed> $queue responses, throwables, promises or callables
* @param bool $recordOutermost unshift history instead of pushing it
* @param string $baseUri whether the trailing slash is there is not cosmetic
*/
public function __construct(
array $queue = [],
bool $recordOutermost = false,
string $baseUri = 'https://api.example.com/v1/'
) {
$this->mock = new MockHandler($queue);
$stack = HandlerStack::create($this->mock);
if ($recordOutermost) {
$stack->unshift(Middleware::history($this->history));
} else {
$stack->push(Middleware::history($this->history));
}
$this->client = new Client([
'handler' => $stack,
// The trailing slash in the default is load-bearing. base_uri is
// merged by RFC 3986 reference resolution, which drops the last
// segment of a base path that does not end in one.
'base_uri' => $baseUri,
'headers' => ['X-Csx' => 'seed'],
]);
}
/** @return array<string, mixed> */
public function fetchItems(int $page): array
{
$response = $this->client->get('items', [
RequestOptions::QUERY => ['page' => $page, 'sort' => 'name'],
]);
return json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
}
/** @param array<string, mixed> $payload */
public function createItem(array $payload): ResponseInterface
{
return $this->client->post('items', [
RequestOptions::JSON => $payload,
RequestOptions::HEADERS => ['X-Request-Id' => 'r-1'],
]);
}
/** @param array<string, mixed> $options */
public function get(string $path, array $options = []): ResponseInterface
{
return $this->client->get($path, $options);
}
public function lastRequest(): RequestInterface
{
return $this->history[count($this->history) - 1]['request'];
}
/**
* The mistake worth being able to spot: a stack built with `new` carries
* none of the default middleware, so http_errors never runs.
*
* @param list<mixed> $queue
*/
public static function bareStackClient(array $queue): Client
{
return new Client([
'handler' => new HandlerStack(new MockHandler($queue)),
'base_uri' => 'https://api.example.com/v1/',
]);
}
}
<?php
use Csx\Api;
use GuzzleHttp\Exception\BadResponseException;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\ResponseException;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\RequestOptions;
use PHPUnit\Framework\TestCase;
final class ApiTest extends TestCase
{
public function testTheQueueIsServedInOrderAndTheRealClientStillRuns(): void
{
$api = new Api([
new Response(200, ['Content-Type' => 'application/json'], '{"page":1}'),
new Response(200, ['Content-Type' => 'application/json'], '{"page":2}'),
]);
$this->assertSame(['page' => 1], $api->fetchItems(1));
$this->assertSame(['page' => 2], $api->fetchItems(2));
// Nothing about the client was stubbed out: base_uri resolution, the
// query option and the default headers all ran for real.
$this->assertSame(
'https://api.example.com/v1/items?page=2&sort=name',
(string) $api->lastRequest()->getUri()
);
$this->assertSame('seed', $api->lastRequest()->getHeaderLine('X-Csx'));
$this->assertCount(0, $api->mock);
// The MockHandler can answer "what was sent" too, but only for the
// most recent request. History is what holds both exchanges.
$this->assertCount(2, $api->history);
$this->assertSame($api->lastRequest(), $api->mock->getLastRequest());
}
public function testTheQueueIsNotARouterAndNeverLooksAtTheRequest(): void
{
// The trap. MockHandler pops the next entry whatever you asked for, so
// a test can pass while the code under test calls the wrong endpoint
// with the wrong verb. Only the recorded request tells you what was
// sent; the response proves nothing.
$api = new Api([new Response(201, [], 'from the queue')]);
$response = $api->client->request('DELETE', 'nothing/like/items');
$this->assertSame(201, $response->getStatusCode());
$this->assertSame('from the queue', (string) $response->getBody());
$this->assertSame('DELETE', $api->lastRequest()->getMethod());
$this->assertSame('/v1/nothing/like/items', $api->lastRequest()->getUri()->getPath());
}
public function testBaseUriIsMergedByReferenceResolutionNotConcatenation(): void
{
$api = new Api([new Response(200), new Response(200)]);
$api->get('items');
$this->assertSame('/v1/items', $api->history[0]['request']->getUri()->getPath());
// Same-looking path, different URL: an absolute-path reference
// replaces the base path instead of appending to it.
$api->get('/items');
$this->assertSame('/items', $api->history[1]['request']->getUri()->getPath());
// And the trailing slash on base_uri is not decoration. Without it
// "/v1" is the last segment rather than a directory, so resolution
// drops it and the request silently leaves the API version behind.
$unslashed = new Api([new Response(200)], baseUri: 'https://api.example.com/v1');
$unslashed->get('items');
$this->assertSame('/items', $unslashed->lastRequest()->getUri()->getPath());
}
public function testHttpErrorsTurnsA500IntoAServerExceptionThatKeepsTheBody(): void
{
$api = new Api([new Response(500, [], '{"error":"nope"}')]);
try {
$api->get('boom');
$this->fail('a 500 should have thrown');
} catch (ServerException $exception) {
// The exception is the response holder, so the body survives the
// throw and is still decodable — summarising it for the message
// puts the stream position back where it found it.
$this->assertSame(500, $exception->getResponse()->getStatusCode());
$this->assertSame('{"error":"nope"}', $exception->getResponse()->getBody()->getContents());
$this->assertSame('/v1/boom', $exception->getRequest()->getUri()->getPath());
$this->assertStringContainsString('500 Internal Server Error', $exception->getMessage());
// A ResponseException is constructed with the status as its code.
$this->assertSame(500, $exception->getCode());
}
}
public function testGetResponseIsNoLongerOnRequestExceptionInGuzzle8(): void
{
// Measured, and the opposite of every Guzzle 7 snippet: getResponse()
// and hasResponse() are no longer declared on RequestException, and
// hasResponse() is gone from the hierarchy outright.
$this->assertFalse(method_exists(RequestException::class, 'getResponse'));
$this->assertFalse(method_exists(RequestException::class, 'hasResponse'));
$this->assertFalse(method_exists(ResponseException::class, 'hasResponse'));
$this->assertTrue(method_exists(ResponseException::class, 'getResponse'));
// getResponse() moved down to ResponseException, where the return type
// is not nullable — a nullable one would stringify with a leading "?"
// — so the `if ($e->hasResponse())` guard has nothing left to guard.
$this->assertSame(
'Psr\Http\Message\ResponseInterface',
(string) (new ReflectionMethod(ResponseException::class, 'getResponse'))->getReturnType()
);
// So the documented Guzzle 7 idiom is a fatal Error on 8, not a
// deprecation: nothing warns you, the call site just dies.
$bare = new RequestException('connection reset', new Request('GET', 'https://api.example.com/v1/items'));
try {
$bare->getResponse();
$this->fail('RequestException should no longer have getResponse');
} catch (Error $error) {
$this->assertSame(
'Call to undefined method GuzzleHttp\Exception\RequestException::getResponse()',
$error->getMessage()
);
}
$api = new Api([new Response(500, [], '{"error":"nope"}')]);
try {
$api->get('boom');
$this->fail('a 500 should have thrown');
} catch (ResponseException $exception) {
// ServerException extends BadResponseException extends
// ResponseException extends RequestException, so an old
// `catch (RequestException $e)` still fires on a 500. Catching the
// class that declares getResponse is what makes the body readable
// again without reflection or a hasResponse() guard.
$this->assertInstanceOf(ServerException::class, $exception);
$this->assertInstanceOf(BadResponseException::class, $exception);
$this->assertInstanceOf(RequestException::class, $exception);
$this->assertSame('{"error":"nope"}', (string) $exception->getResponse()->getBody());
}
}
public function testHttpErrorsFalseGivesYouTheResponseInstead(): void
{
$api = new Api([new Response(500, [], '{"error":"nope"}')]);
$response = $api->get('boom', [RequestOptions::HTTP_ERRORS => false]);
$this->assertSame(500, $response->getStatusCode());
$this->assertSame('{"error":"nope"}', (string) $response->getBody());
}
public function testAQueuedThrowableIsThrownExactlyAsQueued(): void
{
$queued = new RequestException(
'connection reset',
new Request('GET', 'https://api.example.com/v1/items')
);
$api = new Api([$queued]);
try {
$api->get('items');
$this->fail('the queued exception should have been thrown');
} catch (RequestException $exception) {
// Not a copy and not re-wrapped: the same object comes back out,
// which is how you test a transport failure with no network.
$this->assertSame($queued, $exception);
// A bare RequestException has no response side at all now, so
// there is nothing to interrogate except the request.
$this->assertNotInstanceOf(ResponseException::class, $exception);
$this->assertSame('/v1/items', $exception->getRequest()->getUri()->getPath());
}
}
public function testPushedHistoryRecordsThePreparedRequestButNotTheError(): void
{
$api = new Api([new Response(201, [], '{"id":7}')]);
$api->createItem(['name' => 'widget', 'tags' => ['a']]);
$request = $api->lastRequest();
$body = (string) $request->getBody();
$this->assertSame('POST', $request->getMethod());
$this->assertSame('{"name":"widget","tags":["a"]}', $body);
$this->assertSame('application/json', $request->getHeaderLine('Content-Type'));
// Content-Length is prepare_body's contribution, and a pushed history
// sits inside it, so this is the finished wire request.
$this->assertSame((string) strlen($body), $request->getHeaderLine('Content-Length'));
$this->assertSame('r-1', $request->getHeaderLine('X-Request-Id'));
$this->assertSame('seed', $request->getHeaderLine('X-Csx'));
$this->assertSame('GuzzleHttp/8', $request->getHeaderLine('User-Agent'));
// The cost of that position: http_errors is further out, so the entry
// for a failed exchange looks like a success.
$failing = new Api([new Response(500, [], '{"error":"nope"}')]);
try {
$failing->get('boom');
} catch (ServerException) {
}
$this->assertNull($failing->history[0]['error']);
$this->assertSame(500, $failing->history[0]['response']->getStatusCode());
}
public function testUnshiftedHistoryRecordsTheErrorButLosesContentLength(): void
{
$api = new Api([new Response(201, [], '{"id":7}')], recordOutermost: true);
$api->createItem(['name' => 'widget', 'tags' => ['a']]);
$request = $api->lastRequest();
// Same call, different recorded request, purely because of stack
// position. Content-Type survives out here because the client applies
// the json option's conditional header itself; Content-Length does not,
// so asserting on it fails for a reason unrelated to the code
// under test.
$this->assertSame('{"name":"widget","tags":["a"]}', (string) $request->getBody());
$this->assertSame('application/json', $request->getHeaderLine('Content-Type'));
$this->assertFalse($request->hasHeader('Content-Length'));
// What you gain: this side of http_errors, the failure is visible.
$failing = new Api([new Response(500, [], '{"error":"nope"}')], recordOutermost: true);
try {
$failing->get('boom');
} catch (ServerException) {
}
$this->assertInstanceOf(ServerException::class, $failing->history[0]['error']);
$this->assertNull($failing->history[0]['response']);
}
public function testADryQueueRejectsWithAPlainOutOfBoundsException(): void
{
$api = new Api([new Response(200)]);
$api->get('items');
// Measured, against the assumption that a handler throwing means the
// call site throws: Client::transfer catches everything the handler
// raises and returns a rejected promise, so an async call comes back
// normally and the failure only lands when the promise is waited on.
$promise = $api->client->getAsync('items');
$this->assertSame('rejected', $promise->getState());
try {
$promise->wait();
$this->fail('the dry queue should have thrown');
} catch (OutOfBoundsException $exception) {
// A plain SPL exception, deliberately outside Guzzle's hierarchy,
// because an empty queue is a broken test rather than a transport
// failure — so a catch block for GuzzleException will not see it.
$this->assertStringContainsString('Mock queue is empty', $exception->getMessage());
$this->assertNotInstanceOf(GuzzleException::class, $exception);
}
}
public function testABareHandlerStackSilentlyDropsHttpErrors(): void
{
// `new HandlerStack($mock)` carries none of the default middleware, so
// http_errors never runs and every error-path test passes for the
// wrong reason. HandlerStack::create is what makes the 500 above throw.
$client = Api::bareStackClient([new Response(500, [], '{"error":"nope"}')]);
$response = $client->get('boom');
$this->assertSame(500, $response->getStatusCode());
}
}