PHPackages                             phpdot/http - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [HTTP &amp; Networking](/categories/http)
4. /
5. phpdot/http

ActiveLibrary[HTTP &amp; Networking](/categories/http)

phpdot/http
===========

PSR-7 HTTP messages, responses, and uploads for modern PHP.

v0.1.0(4w ago)047MITPHPPHP &gt;=8.5

Since Jul 18Pushed 1mo agoCompare

[ Source](https://github.com/phpdot/http)[ Packagist](https://packagist.org/packages/phpdot/http)[ RSS](/packages/phpdot-http/feed)WikiDiscussions main Synced 5d ago

READMEChangelogDependencies (35)Versions (14)Used By (7)

phpdot/http
===========

[](#phpdothttp)

Advanced HTTP library for PHP. **Self-hosted PSR-7/17** — no third-party message implementation. Framework-agnostic, coroutine-safe.

Install
-------

[](#install)

```
composer require phpdot/http
```

What It Is
----------

[](#what-it-is)

A **self-hosted PSR-7/17 implementation** with a rich, typed API on top — it ships its own message classes, so there's no `nyholm`/`guzzle`/`diactoros` under the hood:

- **PSR-7 messages** — own `ServerRequest`, `Response`, `Stream`, `Uri`, `UploadedFile`, conformance-tested against `php-http/psr7-integration-tests`
- **PSR-17 factories** — `ResponseFactory` implements all five factory interfaces
- **Request** — ergonomic decorator over `ServerRequestInterface`: typed input, content negotiation, trusted proxies, method override
- **ResponseFactory helpers** — JSON, HTML, file downloads (content-based MIME + Range), redirects, cache headers, RFC 9457 problem details, streaming &amp; SSE
- **Cookie** — immutable value object per RFC 6265
- **HTTP Exceptions** — typed exceptions with RFC 9457 Problem Details
- **IpUtils / StatusText** — IPv4/IPv6 subnet checking; complete RFC 9110 status mapping
- **Coroutine-safe** — immutable value objects, zero static state; the singleton factory is safe to share across Swoole coroutines

Dependencies
------------

[](#dependencies)

```
php >= 8.4
psr/http-message ^2.0
psr/http-factory ^1.0
league/mime-type-detection ^1.16

```

PSR interfaces plus a MIME database. **No third-party PSR-7 implementation.**

---

Architecture
------------

[](#architecture)

 ```
graph TD
    PSR7["phpdot/http PSR-7Own ServerRequest / Response / Stream /Uri / UploadedFile — conformance-tested,no third-party implementation"]

    subgraph Inbound
        direction TB
        REQ["PHPdot Http RequestDecorates the PSR-7 request:typed input accessors,content negotiation,trusted-proxy resolution,method override,cookie and file helpers,URL pattern matching"]
    end

    APP["Your applicationReads the Request,calls the factory"]

    subgraph Outbound
        direction TB
        FACTORY["PHPdot Http ResponseFactoryPSR-17 factory plus helpers:json, html, text, download,file with Range support,redirect, noContent,cache headers (ETag, Last-Modified),cookies, RFC 9457 problem details"]
    end

    OUT["ResponseInterfaceReturned to theHTTP transport"]

    PSR7 --> Inbound
    Inbound --> APP
    APP --> Outbound
    Outbound --> OUT
```

      Loading ---

Request
-------

[](#request)

Wraps any `ServerRequestInterface`. Implements `ServerRequestInterface` itself — drop-in compatible.

```
use PHPdot\Http\Message\Request;

$request = new Request($psrRequest);
```

### Input — Typed Accessors

[](#input--typed-accessors)

```
$request->query('page', 1);              // query string with default
$request->input('email');                 // parsed body
$request->all();                          // merged query + body (body wins)

$request->only(['email', 'name']);        // pick specific keys
$request->except(['password']);           // exclude keys

$request->has('email');                   // exists and not empty
$request->hasAny(['email', 'phone']);     // at least one exists
$request->filled('name');                 // exists, not empty, not null
$request->missing('token');              // not present at all
```

### Typed — Safe, Never Throws

[](#typed--safe-never-throws)

```
$request->string('name', '');            // string, default on failure
$request->integer('page', 1);            // int via filter_var
$request->float('price', 0.0);          // float via filter_var
$request->boolean('active', false);      // "true","1","on","yes" → true
$request->array('tags', []);             // array or default
$request->date('created_at');            // DateTimeImmutable or null
$request->enum('color', Color::class);   // BackedEnum or null
```

### Method Override

[](#method-override)

For HTML forms that only support GET/POST:

```
$request->method();                       // intended method (_method or X-HTTP-Method-Override)
$request->realMethod();                   // actual HTTP method
$request->isGet();                        // check intended method
$request->isPost();
$request->isPut();
$request->isDelete();
```

### Headers

[](#headers)

```
$request->header('Accept');               // first value or null
$request->headers('Accept');              // all values as array
$request->bearerToken();                  // "Bearer " → token
$request->basicCredentials();             // ['username' => ..., 'password' => ...]
$request->userAgent();
$request->contentType();                  // without parameters
$request->contentLength();
```

### Content Negotiation

[](#content-negotiation)

```
$request->accepts('application/json');                   // bool
$request->wantsJson();                                    // Accept contains json
$request->preferredType(['text/html', 'application/json']); // best match or null
$request->preferredLanguage(['en', 'ar', 'fr']);            // best match or null
```

### Client &amp; Connection

[](#client--connection)

```
$request->ip();                           // trusted-proxy-aware
$request->ips();                          // full X-Forwarded-For chain
$request->scheme();                       // trusted-proxy-aware
$request->host();                         // trusted-proxy-aware
$request->port();                         // trusted-proxy-aware
$request->isSecure();                     // trusted-proxy-aware
$request->isXhr();                        // XMLHttpRequest
$request->isJson();                       // Content-Type contains json
$request->isPrefetch();                   // Purpose/Sec-Purpose: prefetch
```

### Trusted Proxies

[](#trusted-proxies)

Configure via `HttpConfig` and pass it into the `Request` constructor:

```
$config = new HttpConfig(
    trustedProxies: ['10.0.0.0/8', '172.16.0.0/12'],
    trustedHeaders: Request::HEADER_X_FORWARDED_ALL,
);

$request = new Request($psr7, $config);
```

Inside a phpdot/container application, `HttpConfig` is hydrated from `config/http.php` and `ResponseFactory` (and any other DI consumers) get it injected automatically. The boundary code that builds `Request` from raw input passes the same `HttpConfig` to its constructor.

After configuration, `ip()`, `scheme()`, `host()`, `port()`, `isSecure()` automatically read from forwarded headers when the request comes through a trusted proxy.

Header constants:

```
Request::HEADER_X_FORWARDED_FOR   // 0b00001
Request::HEADER_X_FORWARDED_HOST  // 0b00010
Request::HEADER_X_FORWARDED_PORT  // 0b00100
Request::HEADER_X_FORWARDED_PROTO // 0b01000
Request::HEADER_FORWARDED         // 0b10000 (RFC 7239)
Request::HEADER_X_FORWARDED_ALL   // 0b01111
```

### URL

[](#url)

```
$request->path();                         // /users/42
$request->url();                          // https://example.com/users/42
$request->fullUrl();                      // https://example.com/users/42?page=2
$request->segment(1);                     // "users" (1-indexed)
$request->segments();                     // ["users", "42"]
$request->is('api/*');                    // wildcard pattern matching
$request->is('admin/**');                 // ** matches multiple segments
```

### Route Parameters

[](#route-parameters)

```
$request->route('id');                    // reads from getAttribute('id')
```

### Files &amp; Cookies

[](#files--cookies)

```
$request->file('avatar');                 // UploadedFileInterface or null
$request->hasFile('avatar');              // exists AND no upload error
$request->allFiles();                     // all uploaded files

$request->cookie('session_id');           // cookie value or null
$request->cookies();                      // all cookies
$request->hasCookie('session_id');        // bool
```

### Escape Hatch

[](#escape-hatch)

```
$request->psr();                          // inner ServerRequestInterface
```

---

ResponseFactory
---------------

[](#responsefactory)

Implements all five PSR-17 factory interfaces and builds phpdot/http's own PSR-7 responses — plus a rich set of helpers.

```
use PHPdot\Http\Factory\ResponseFactory;

$factory = new ResponseFactory();   // self-contained (optionally: new ResponseFactory($httpConfig))
```

### Basic Responses

[](#basic-responses)

```
$factory->json(['status' => 'ok']);                    // 200, application/json
$factory->json(['user' => $user], 201);                // custom status
$factory->json($data, 200, JSON_PRETTY_PRINT);         // custom options

$factory->html('Hello');                      // 200, text/html; charset=UTF-8
$factory->text('plain text');                           // 200, text/plain; charset=UTF-8
$factory->xml('');                               // 200, application/xml; charset=UTF-8

$factory->redirect('/login');                           // 302 redirect
$factory->redirect('/new-url', 301);                    // permanent redirect

$factory->noContent();                                  // 204
$factory->raw(202);                                     // empty response with status
```

### File Responses

[](#file-responses)

```
// Force download — UTF-8 filename support (RFC 5987)
$factory->download('/path/to/تقرير.pdf');
// Content-Disposition: attachment; filename="-.pdf"; filename*=UTF-8''%D8%AA%D9%82%D8%B1%D9%8A%D8%B1.pdf

// Inline with Range support (RFC 7233)
$factory->file('/path/to/video.mp4', $request->psr());
// No Range    → 200, full content, Accept-Ranges: bytes
// Range       → 206, partial content, Content-Range header
// Bad Range   → 416, Range Not Satisfiable
```

### Cache Helpers

[](#cache-helpers)

```
$response = $factory->withCache($response, maxAge: 3600, public: true, immutable: true);
// Cache-Control: public, max-age=3600, immutable

$response = $factory->withEtag($response, md5($content));
// ETag: "abc123"

$response = $factory->withEtag($response, md5($content), weak: true);
// ETag: W/"abc123"

$response = $factory->withLastModified($response, $date);
// Last-Modified: Thu, 01 Jan 2026 00:00:00 GMT

if ($factory->isNotModified($request->psr(), $response)) {
    return $factory->notModified();  // 304
}
```

### Cookies

[](#cookies)

`ResponseFactory::cookie()` produces a Cookie pre-populated with defaults from the injected `HttpConfig`'s `cookie` block (configured via `config/http.php`). Override individual fields with the Cookie's `with*()` chain.

```
// Session cookie — defaults applied (secure, httpOnly, sameSite from CookieConfig)
$cookie = $factory->cookie('session', $token);

// Long-lived cookie — explicit max-age
$cookie = $factory->cookie('remember', $token)
    ->withMaxAge(86400 * 30);

// Override defaults per-cookie when needed
$cookie = $factory->cookie('csrf', $csrfToken)
    ->withSameSite('Strict');

$response = $factory->withCookie($response, $cookie);
$response = $factory->withoutCookie($response, 'old_session');
```

Defaults come from `config/http.php`:

```
return [
    'cookie' => [
        'secure'      => true,
        'httpOnly'    => true,
        'sameSite'    => 'Lax',
        'path'        => '/',
        'domain'      => '',
        'partitioned' => false,
    ],
];
```

### Problem Details (RFC 9457)

[](#problem-details-rfc-9457)

```
$factory->problem(
    status: 422,
    detail: 'Email is already taken',
    extensions: ['field' => 'email'],
);
// Content-Type: application/problem+json
// {"type":"about:blank","title":"Unprocessable Content","status":422,"detail":"Email is already taken","field":"email"}
```

### Streaming &amp; Server-Sent Events

[](#streaming--server-sent-events)

`StreamedResponse` produces its body incrementally at send time instead of buffering it — for large/generated output and SSE. It's a full PSR-7 response; the server transport (`server-swoole`) detects `StreamedResponseInterface` and pumps chunks over a long-lived connection.

```
use PHPdot\Http\Response\SseWriter;

// Generic streaming — $write returns false once the client disconnects
return $factory->stream(function (callable $write): void {
    foreach ($rows as $row) {
        $write(json_encode($row) . "\n");
    }
});

// Server-Sent Events — proxy-safe headers set automatically
return $factory->sse(function (SseWriter $sse) use ($request): void {
    $lastId = $request->getHeaderLine('Last-Event-ID');   // resume support
    while ($sse->send(data: json_encode($event), event: 'update', id: (string) $event->id)) {
        $sse->comment();                                   // heartbeat — survives proxy idle timeouts
        \Swoole\Coroutine::sleep(1);
    }
});
```

SSE responses set `Content-Type: text/event-stream`, `Cache-Control: no-cache, no-transform` (stops Cloudflare / CDNs buffering or gzipping the stream), `Connection: keep-alive`, and `X-Accel-Buffering: no`.

---

Cookie
------

[](#cookie)

Pure immutable value object. Builds and parses Set-Cookie headers per RFC 6265.

```
use PHPdot\Http\Cookie\Cookie;

// Direct construction — uses safe defaults: secure=true, httpOnly=true, sameSite=Lax, path='/'
$cookie = new Cookie('session', 'abc123');

// All attributes explicit
$cookie = new Cookie(
    name:     'session',
    value:    'abc123',
    path:     '/',
    domain:   '.example.com',
    secure:   true,
    httpOnly: true,
    sameSite: 'Strict',
);

// Builder chain on top
$cookie = (new Cookie('session', 'abc123'))
    ->withDomain('.example.com')
    ->withSameSite('Strict')
    ->withMaxAge(86400);

// Serialize
$header = $cookie->toHeaderString();
// session=abc123; Path=/; Domain=.example.com; Max-Age=86400; Secure; HttpOnly; SameSite=Strict

// Parse
$parsed = Cookie::fromHeaderString($header);

// Inspect
$cookie->getName();        // "session"
$cookie->getValue();       // "abc123"
$cookie->isSecure();       // true
$cookie->isHttpOnly();     // true
$cookie->isExpired();      // false
```

For app-wide defaults from `config/http.php`, prefer `ResponseFactory::cookie()` (above).

### Validation

[](#validation)

- SameSite=None requires Secure
- Partitioned requires Secure
- Cookie names validated per RFC 6265

---

HTTP Exceptions
---------------

[](#http-exceptions)

All extend `HttpException`. All support RFC 9457 Problem Details.

```
use PHPdot\Http\Exception\NotFoundException;
use PHPdot\Http\Exception\UnprocessableEntityException;
use PHPdot\Http\Exception\TooManyRequestsException;
use PHPdot\Http\Exception\MethodNotAllowedException;

throw new NotFoundException('User not found');

throw new UnprocessableEntityException(
    errors: ['email' => 'Already taken', 'name' => 'Required'],
    message: 'Validation failed',
);

throw new TooManyRequestsException(
    retryAfter: 60,
    message: 'Rate limit exceeded',
);

throw new MethodNotAllowedException(
    allowedMethods: ['GET', 'POST'],
);
```

### Problem Details

[](#problem-details)

```
$exception->toProblemDetails();
// [
//     'type' => 'about:blank',
//     'title' => 'Not Found',
//     'status' => 404,
//     'detail' => 'User not found',
// ]
```

### Available Exceptions

[](#available-exceptions)

ClassStatus`BadRequestException`400`UnauthorizedException`401`ForbiddenException`403`NotFoundException`404`MethodNotAllowedException`405`RequestTimeoutException`408`ConflictException`409`PayloadTooLargeException`413`UnsupportedMediaTypeException`415`UnprocessableEntityException`422`TooManyRequestsException`429`ServerErrorException`500`BadGatewayException`502`ServiceUnavailableException`503`GatewayTimeoutException`504---

IpUtils
-------

[](#iputils)

Standalone IPv4/IPv6 utility.

```
use PHPdot\Http\Support\IpUtils;

IpUtils::inRange('10.0.0.5', '10.0.0.0/8');          // true
IpUtils::inRange('::1', '::1/128');                    // true
IpUtils::matches('10.0.0.5', ['10.0.0.0/8', '172.16.0.0/12']); // true
IpUtils::isPrivate('10.0.0.1');                        // true (RFC 1918)
IpUtils::isPrivate('8.8.8.8');                         // false
IpUtils::isIPv4('192.168.1.1');                        // true
IpUtils::isIPv6('::1');                                // true
```

---

StatusText
----------

[](#statustext)

Complete RFC 9110 mapping.

```
use PHPdot\Http\Support\StatusText;

StatusText::get(200);  // "OK"
StatusText::get(404);  // "Not Found"
StatusText::get(418);  // "I'm a Teapot"
StatusText::get(999);  // ""
```

---

Package Structure
-----------------

[](#package-structure)

```
src/
├── Message/          Own PSR-7 messages — Request (decorator) · ServerRequest · Response ·
│                     Stream · Uri · UploadedFile · MessageTrait
├── Response/         JsonResponse · HtmlResponse · RedirectResponse ·
│                     StreamedResponse · StreamedResponseInterface · SseWriter
├── Factory/          ResponseFactory — PSR-17 factories + all response helpers
├── Cookie/           Cookie · CookieConfig
├── Support/          IpUtils · StatusText · HttpConfig
└── Exception/        HttpException + 16 typed HTTP exceptions

```

---

Framework Integration
---------------------

[](#framework-integration)

### `HttpConfig` — auto-bound DTO

[](#httpconfig--auto-bound-dto)

`HttpConfig` is tagged `#[Config('http')]`, so when used inside the phpdot framework (with `phpdot/package` + `phpdot/config`), it's hydrated automatically from `config/http.php`. It carries two concerns: trusted-proxy settings (consumed by `Request`) and a nested `CookieConfig` (consumed by `Cookie`):

```
// config/http.php
use PHPdot\Http\Message\Request;

return [
    'trustedProxies' => [],
    'trustedHeaders' => 0,
    'cookie' => [
        'secure'      => true,
        'httpOnly'    => true,
        'sameSite'    => 'Lax',
        'path'        => '/',
        'domain'      => '',
        'partitioned' => false,
    ],

    'development' => [
        'cookie' => ['secure' => false],
    ],
    'staging' => [],
    'production' => [],
];
```

FieldTypeDefault`trustedProxies``list` (IPs or CIDR ranges)`[]``trustedHeaders``int` (bitmask of `Request::HEADER_*` constants)`0``cookie``CookieConfig` (nested DTO)sensible defaults — see below`HttpConfig::cookie` is a nested typed DTO; `phpdot/config` v1.1+ recursively hydrates it from the `cookie` sub-array.

**Configure trusted proxies for any deployment behind a proxy / load balancer / CDN.** Without it, `Request::ip()`, `Request::isSecure()`, `Request::host()`, etc. silently return wrong values.

### `CookieConfig` — baseline cookie defaults

[](#cookieconfig--baseline-cookie-defaults)

Every `ResponseFactory::cookie()` call reads its defaults from the injected `HttpConfig`'s nested `CookieConfig`. Apps configure once in `config/http.php`; per-cookie deviations use Cookie's `with*()` chain as usual.

FieldTypeDefault`secure``bool``true``httpOnly``bool``true``sameSite``'Strict' | 'Lax' | 'None'``'Lax'``path``string``'/'``domain``string``''``partitioned``bool``false`Per-environment override is the natural place to flip `secure` for HTTP development:

```
// config/http.php
'cookie' => ['secure' => true, /* prod-safe defaults */],
'development' => ['cookie' => ['secure' => false]],
```

### CloudFlare example

[](#cloudflare-example)

```
// config/http.php
use PHPdot\Http\Message\Request;

return [
    'trustedProxies' => [
        // CloudFlare IPv4 (https://www.cloudflare.com/ips-v4)
        '173.245.48.0/20', '103.21.244.0/22', '103.22.200.0/22', '103.31.4.0/22',
        '141.101.64.0/18', '108.162.192.0/18', '190.93.240.0/20', '188.114.96.0/20',
        '197.234.240.0/22', '198.41.128.0/17', '162.158.0.0/15', '104.16.0.0/13',
        '104.24.0.0/14', '172.64.0.0/13', '131.0.72.0/22',
        // CloudFlare IPv6 (https://www.cloudflare.com/ips-v6)
        '2400:cb00::/32', '2606:4700::/32', '2803:f800::/32', '2405:b500::/32',
        '2405:8100::/32', '2a06:98c0::/29', '2c0f:f248::/32',
    ],
    'trustedHeaders' => Request::HEADER_X_FORWARDED_ALL,
    'cookie' => [
        'secure'   => true,
        'sameSite' => 'Lax',
        'domain'   => '.example.com',
    ],
];
```

CloudFlare populates `X-Forwarded-For`, so `HEADER_X_FORWARDED_ALL` is the right bitmask. CIDR ranges occasionally change — refresh quarterly or fetch from CloudFlare's API at deploy time.

### Bootstrap pattern

[](#bootstrap-pattern)

`HttpConfig` flows entirely through DI — no boot hook to call. The container resolves `HttpConfig` from `config/http.php`, injects it into `ResponseFactory` (so `cookie()` reads its defaults) and into the boundary code that builds `Request` instances (so `ip()`, `scheme()`, etc. respect trusted proxies). Zero static state, no `apply()` step.

```
// Worker boot
$container = (new ContainerBuilder())
    ->addDefinitionsFromFile(vendor('phpdot/definitions.php'))
    ->build();

// Per request — boundary code
$psr7    = (new ServerRequestCreator(...))->fromGlobals();
$request = new Request($psr7, $container->get(HttpConfig::class));

// Or get HttpConfig injected into RequestFactory / your dispatcher
```

### `ResponseFactory` — auto-bound to all 5 PSR-17 interfaces

[](#responsefactory--auto-bound-to-all-5-psr-17-interfaces)

`ResponseFactory` is tagged `#[Singleton]` plus `#[Binds(...)]` for each of the PSR-17 factory interfaces it implements:

```
Psr\Http\Message\ResponseFactoryInterface       → ResponseFactory
Psr\Http\Message\ServerRequestFactoryInterface  → ResponseFactory
Psr\Http\Message\StreamFactoryInterface         → ResponseFactory
Psr\Http\Message\UriFactoryInterface            → ResponseFactory
Psr\Http\Message\UploadedFileFactoryInterface   → ResponseFactory

```

So injecting any of those interfaces in your controllers / services resolves to `ResponseFactory` automatically. No manual binding needed.

### Standalone usage (without phpdot framework)

[](#standalone-usage-without-phpdot-framework)

The lifecycle attributes are inert at runtime — `phpdot/container` is a `require-dev` dependency only. Without DI, just instantiate `ResponseFactory` and `Request` directly. Both accept an optional `HttpConfig` parameter and default to safe values when omitted:

```
$config = new HttpConfig(
    trustedProxies: ['10.0.0.0/8'],
    trustedHeaders: Request::HEADER_X_FORWARDED_ALL,
);

$factory = new ResponseFactory($config);
$request = new Request($psr7, $config);

// Or, with no config at all — sensible defaults are baked into HttpConfig and CookieConfig
$factory = new ResponseFactory();
$request = new Request($psr7);
```

The auto-binding only activates when the framework's manifest scanner reads the attributes.

---

Usage with Frameworks
---------------------

[](#usage-with-frameworks)

### With phpdot/routing

[](#with-phpdotrouting)

```
$router->get('/users/{id:int}', function (ServerRequestInterface $serverRequest, int $id) use ($factory): ResponseInterface {
    $request = new Request($serverRequest);

    if ($request->wantsJson()) {
        return $factory->json(['id' => $id, 'name' => 'Omar']);
    }

    return $factory->html("User {$id}");
});
```

### With Slim

[](#with-slim)

```
$app->get('/users/{id}', function ($request, $response, $args) use ($factory) {
    $req = new Request($request);
    return $factory->json(['id' => $req->integer('id')]);
});
```

### With Any PSR-15 Middleware

[](#with-any-psr-15-middleware)

```
final class ApiErrorHandler implements MiddlewareInterface
{
    public function __construct(private ResponseFactory $factory) {}

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        try {
            return $handler->handle($request);
        } catch (HttpException $e) {
            return $this->factory->problem(
                status: $e->getStatusCode(),
                detail: $e->getDetail(),
                extensions: $e->getExtensions(),
            );
        }
    }
}
```

---

Development
-----------

[](#development)

```
composer test        # PHPUnit (425 tests + PSR-7 conformance suite)
composer analyse     # PHPStan level 10
composer cs-fix      # PHP-CS-Fixer
composer cs-check    # Dry run
composer check       # All three
```

License
-------

[](#license)

MIT

###  Health Score

41

—

FairBetter than 87% of packages

Maintenance93

Actively maintained with recent releases

Popularity5

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~9 days

Recently: every ~20 days

Total

13

Last Release

28d ago

Major Versions

v1.1.0 → v2.0.02026-04-10

v2.6.1 → v3.0.02026-07-01

PHP version history (3 changes)v1.0.0PHP &gt;=8.3

v2.6.1PHP &gt;=8.4

v0.1.0PHP &gt;=8.5

### Community

Maintainers

![](https://www.gravatar.com/avatar/62e82421bda4b5d6ba9a47ba6d88caca060dcd0d1a2862f351f3a97657385db0?d=identicon)[phpdot](/maintainers/phpdot)

---

Top Contributors

[![phpdot](https://avatars.githubusercontent.com/u/252500?v=4)](https://github.com/phpdot "phpdot (13 commits)")

---

Tags

httpresponserequestpsr-7message

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/phpdot-http/health.svg)

```
[![Health](https://phpackages.com/badges/phpdot-http/health.svg)](https://phpackages.com/packages/phpdot-http)
```

###  Alternatives

[guzzlehttp/psr7

PSR-7 message implementation that also provides common utility methods

7.9k1.1B4.4k](/packages/guzzlehttp-psr7)[tempest/framework

The PHP framework that gets out of your way.

2.3k37.6k20](/packages/tempest-framework)[shopware/core

Shopware platform is the core for all Shopware ecommerce products.

595.8M668](/packages/shopware-core)[shopware/platform

The Shopware e-commerce core

3.4k1.5M3](/packages/shopware-platform)[sunrise/http-router

A powerful solution as the foundation of your project.

16852.3k12](/packages/sunrise-http-router)[chillerlan/php-httpinterface

A PSR-7/17/18 http message/client implementation

1419.2k8](/packages/chillerlan-php-httpinterface)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
