Skip to content

Repository files navigation

HTTP CLI

Serverless HTTP client - make requests to PHP scripts on the CLI.

This library lets you make HTTP requests to PHP applications without running a web server. Instead of Apache or nginx, it executes your PHP scripts directly via the command line while emulating the full HTTP environment ($_GET, $_POST, $_SERVER, $_SESSION, headers, cookies, etc.).

Perfect for testing, CI pipelines, unreleased deployment or any scenario where spinning up a web server is overkill or not possible.

Installation

composer require n5s/http-cli

Usage

usen5s\HttpCli\Client;
usen5s\HttpCli\RequestOptions;
$client = newClient('/path/to/your/app');
// Simple GET request$response = $client->request('GET', 'https://example.com/api/users');
echo$response->getContent();
// POST with JSON$response = $client->request('POST', '/api/users',
RequestOptions::create()
->json(['name' => 'John', 'email' => 'john@example.com'])
->build()
);
// POST with form data$response = $client->request('POST', '/login',
RequestOptions::create()
->formParams(['username' => 'admin', 'password' => 'secret'])
->build()
);

How It Works

When you make a request, the library:

  1. Spawns a PHP CLI process targeting your script
  2. Injects a bootstrap that populates $_GET, $_POST, $_SERVER, $_COOKIE, and $_SESSION
  3. Provides polyfills for specific HTTP context functions: header(), headers_sent(), http_response_code(), etc.
  4. Captures the output and headers, returning a Response object

Your PHP scripts run exactly as they would under a web server, but without one.

Framework Adapters

Use your favorite HTTP client library - just swap in our handler.

Guzzle

composer require guzzlehttp/guzzle
useGuzzleHttp\Client;
useGuzzleHttp\HandlerStack;
usen5s\HttpCli\Guzzle\CliHandler;
usen5s\HttpCli\Client;
$cliClient = newClient('/path/to/your/app');
$handler = newCliHandler($cliClient);
$client = newClient([
'handler' => HandlerStack::create($handler),
]);
// Use Guzzle as normal$response = $client->get('/api/users');
$response = $client->post('/api/users', [
'json' => ['name' => 'John'],
]);

Symfony HttpClient

composer require symfony/http-client
usen5s\HttpCli\Symfony\CliClient;
usen5s\HttpCli\Client;
$cliClient = newClient('/path/to/your/app');
$client = newCliClient($cliClient);
// Use Symfony HttpClient as normal$response = $client->request('GET', '/api/users');
$data = $response->toArray();

WordPress Requests

composer require rmccue/requests
useWpOrg\Requests\Requests;
usen5s\HttpCli\WordPress\Cli;
usen5s\HttpCli\Client;
$cliClient = newClient('/path/to/your/app');
Requests::set_transport([Cli::class]);
Cli::setClient($cliClient);
// Use WordPress Requests as normal$response = Requests::get('/api/users');

Request Options

Build requests with a fluent API:

usen5s\HttpCli\RequestOptions;
$options = RequestOptions::create()
// Body
->json(['key' => 'value']) // JSON payload
->formParams(['field' => 'value']) // Form data (application/x-www-form-urlencoded)
->body('raw content') // Raw body
->multipart([ // Multipart form data
['name' => 'file', 'contents' => 'data', 'filename' => 'test.txt'],
])
// Headers & Auth
->headers(['X-Custom' => 'value'])
->basicAuth('user', 'pass')
->bearerToken('token')
->cookies(['session' => 'abc123'])
// Other
->query(['page' => 1, 'limit' => 10])
->timeout(30.0)
->build();
$response = $client->request('POST', '/api/endpoint', $options);

Response

$response = $client->request('GET', '/api/users');
$response->getStatusCode(); // 200$response->getHeaders(); // ['Content-Type: application/json', ...]$response->getContent(); // Response body as string$response->getSession(); // Session data array$response->getProcess(); // Symfony Process instance (for debugging)

Configuration

$client = newClient(
documentRoot: '/path/to/your/app', // Required: your app's root directory
file: 'index.php', // Entry point (default: index.php)
globalsHandler: null, // GlobalsHandler to customize superglobals
phpExecutable: null, // PHP binary path (auto-detected)
);

Globals Handler

By default, the child PHP process receives a clean set of superglobals built from the HTTP request only. If your application depends on environment variables from the parent process (e.g. APP_ENV, DATABASE_URL), you can use the built-in InheritEnvGlobalsHandler:

usen5s\HttpCli\Client;
usen5s\HttpCli\InheritEnvGlobalsHandler;
$client = newClient(
documentRoot: '/path/to/your/app',
globalsHandler: newInheritEnvGlobalsHandler(),
);

This merges the parent's $_SERVER and $_ENV into the child process. Request-specific variables take precedence over inherited ones.

You can also implement the GlobalsHandler interface to customize superglobals however you need:

usen5s\HttpCli\GlobalsHandler;
finalclass MyGlobalsHandler implements GlobalsHandler
{
publicfunctionhandle(array &$globals): void
{
$globals['_SERVER']['APP_ENV'] = 'testing';
$globals['_ENV']['CUSTOM_VAR'] = 'value';
}
}

Adapter Options Support

Guzzle

OptionSupported
timeout
headers
query
body
json
form_params
multipart
auth
cookies
allow_redirects
http_errors
decode_content
version
sink
on_headers✅ (callback)
on_stats✅ (callback)
connect_timeout❌ ignored
verify❌ ignored
cert❌ ignored
proxy❌ ignored
ssl_key
progress
debug

Symfony HttpClient

OptionSupported
timeout
headers
query
body
json
auth_basic
auth_bearer
max_redirects
verify_peer❌ ignored
verify_host❌ ignored
cafile❌ ignored
proxy❌ ignored
http_version
on_progress
resolve
local_cert
local_pk
ciphers

WordPress Requests

OptionSupported
timeout
useragent
redirects
follow_redirects
auth
cookies
connect_timeout❌ ignored
proxy❌ ignored
verify❌ ignored
verifyname❌ ignored
filename
hooks
max_bytes

Limitations

Running PHP scripts via CLI instead of a web server comes with inherent limitations:

Not Supported

FeatureReason
Persistent connectionsEach request spawns a new PHP process
Keep-aliveNo connection reuse between requests
HTTP/2, HTTP/3CLI execution doesn't use HTTP protocol
WebSocketsRequires persistent connection
Server-Sent EventsRequires streaming connection
Real SSL/TLSNo actual HTTPS handshake (URLs are parsed, not connected)
Output streamingResponse is captured after script completes
fastcgi_finish_request()FPM-specific function
APCu user cacheNot shared between CLI processes
OPcache benefitsEach process starts fresh
Static filesOnly executes PHP - images, fonts, CSS, JS won't be served

Behavioral Differences

  • Performance: Process spawning overhead, but no DNS resolution, TCP/SSL handshake, or network latency
  • $_SERVER values: Some values like SERVER_SOFTWARE will differ from Apache/nginx
  • File uploads: Multipart parts are written to temp files and populated in $_FILES
  • Session handling: Works but uses an in-memory handler, not file-based persistence
  • php://input: Custom stream wrapper provides the request body

License

MIT

About

Serverless HTTP client - make requests to PHP scripts on the command line

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages