Download the PHP package hamaadraza/php-impersonate without Composer
On this page you can find all versions of the php package hamaadraza/php-impersonate. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download hamaadraza/php-impersonate
More information about hamaadraza/php-impersonate
Files in hamaadraza/php-impersonate
Package php-impersonate
Short Description A PHP library for making HTTP requests with browser impersonation
License MIT
Homepage https://github.com/hamaadraza/php-impersonate
Informations about the package php-impersonate
PHP-Impersonate
Make HTTP requests that look like a real browser. PHP-Impersonate wraps curl-impersonate to reproduce the exact TLS and HTTP/2 fingerprints of Chrome, Firefox, Safari, Edge and Tor, so your requests carry a browser's signature instead of the one that gets ordinary HTTP clients blocked.
Contents
- Quick start
- Two engines, one class
- Supported platforms
- Headers
- Errors
- License
Why
Websites increasingly fingerprint the TLS handshake and HTTP/2 frames to tell real browsers from scripts. cURL, Guzzle and friends have a distinctive signature that is trivial to block. PHP-Impersonate sends byte-for-byte the same handshake as a chosen browser version, so your request looks like it came from that browser.
- π 39 browser profiles β Chrome, Firefox, Safari (desktop + iOS), Edge, Tor, OkHttp.
- π Real TLS/JA4 + HTTP/2 fingerprints, not just a
User-Agentstring β taken from curl-impersonate's own table, so both engines send the same bytes. - β‘ Two engines, one class β a zero-setup executable, and an optional in-process FFI path that reuses connections for serious throughput.
- π¦ Batteries included β binaries ship in the package for common platforms.
- π§© One entry point β everything runs through the single
PHPImpersonateclass.
Requirements
- PHP 8.2+ (8.0 and 8.1 are past end-of-life and cannot run the test suite)
- Optional, for the faster FFI engine: the
ffiextension (bundled with PHP; always usable on the CLI).
Installation
That's it on common platforms β the binaries are bundled. On other platforms run a one-time installer.
Quick start
Two engines, one class
PHPImpersonate is the only class you use. Under the hood it has two engines for
reaching curl-impersonate, and by default it transparently picks the faster
one that works in your environment β you never touch a second class.
| FFI engine | Executable engine | |
|---|---|---|
| How | calls libcurl-impersonate in-process via PHP FFI |
runs the bundled curl-impersonate binary per request |
| Performance | no process spawn; keep-alive connections reused across requests | one short-lived process per request |
| Requirements | the ffi extension usable + the shared library (see platforms) |
none β works everywhere |
| Curl options | β same curated set | β same curated set |
[!TIP] Just use
PHPImpersonateβ the default'auto'engine chooses FFI when it's usable, otherwise the executable. Both accept the same options and produce the same fingerprints, so your code is identical either way. They draw the fingerprint from one place β the impersonation table built into curl-impersonate itself (curl_easy_impersonate()on FFI,--impersonateon the executable) β so the two cannot drift apart.
Static helpers β one-liners for one-off requests:
Instance β reuse configuration across requests (the FFI engine keeps its connection alive across calls on the same instance):
All request methods share one signature and return the same Response:
Choosing the engine
Pass engine: to force one, and inspect the choice when you need to:
If the FFI engine misbehaves in a way no exception can describe (a crash), run
php vendor/hamaadraza/php-impersonate/scripts/ffi-diagnose.php: it executes
each stage of the engine in a separate process and reports which one failed,
along with the PHP build and extensions it ran under β paste that into a bug
report.
Under 'auto', the FFI engine is used whenever PHPImpersonate::ffiAvailable()
is true; otherwise the executable engine runs. Both engines accept the exact same
curl options, so the choice never changes behaviour.
ffiAvailable() is true only when the ffi extension is usable β always on the
CLI; other SAPIs also need ffi.enable β and the shared library loads on this
platform (bundled for common platforms;
PHP_IMPERSONATE_LIB overrides it).
[!WARNING] A libcurl compiled into the
phpbinary (ext-curl built in rather than loaded as a module: the official Docker images, some source builds) sits ahead of the bundledlibcurl-impersonatein the dynamic linker's symbol lookup, so the library's own internal calls could land in that stock libcurl β which answersCURLE_UNKNOWN_OPTIONand corrupts the handle. On glibc the library is loaded withRTLD_DEEPBIND, which makes it bind to itself, and the FFI engine works. On musl (Alpine) there is no such flag:ffiAvailable()detects the situation (it checks that the default profile actually applies, not just that the library loads),autouses the executable engine, andffiUnavailableReason()spells it out. Nothing crashes either way. Windows cannot hit this at all: a DLL's imports bind to the module its own import table names, and the library's internal calls never go through one.
Supported platforms
Prebuilt binaries and FFI libraries ship inside the package for the most common platforms, so everything works with no extra steps:
| Platform | Executable | FFI library |
|---|---|---|
| Linux x86_64 (glibc) | bundled | bundled |
| macOS ARM64 (Apple Silicon) | bundled | bundled |
| Windows x86_64 | bundled | bundled |
| Linux x86_64 (musl / Alpine) | on demand | on demand |
| Linux ARM64 (glibc & musl) | on demand | on demand |
| macOS x86_64 (Intel) | on demand | on demand |
On Alpine, note that the official php:*-alpine images do not enable the
ffi extension; add apk add libffi-dev && docker-php-ext-install ffi to use
the FFI engine there, otherwise the executable engine is used.
On an βon demandβ platform, run the installer once after composer require to
download the matching binary and library:
To run it automatically, add it to your project's composer.json (a
dependency's own scripts don't run on install):
The installer is a no-op when the platform is already bundled. Flags:
--no-libs (skip the FFI library), --force (re-download), --version=TAG.
Making requests
GET
POST / PUT / PATCH
Pass an array of data. It is form-encoded by default, or JSON when you set a JSON
Content-Type (see Request bodies).
DELETE / HEAD
Responses
Every request returns a Response:
| Method | Returns | Description |
|---|---|---|
status() |
int |
HTTP status code |
isSuccess() |
bool |
true for 2xx |
body() |
string |
Raw response body |
json($assoc = true, $depth = 512, $flags = 0) |
mixed |
Decoded JSON β throws \JsonException on invalid JSON |
header($name, $default = null) |
?string |
First value of a header (case-insensitive) |
headerAll($name) |
string[] |
All values of a header (use for Set-Cookie) |
hasHeader($name) |
bool |
Whether a header is present (case-insensitive) |
headers() |
array<string, string[]> |
All headers as name β list of values |
effectiveUrl() |
?string |
The URL the transfer ended on, after redirects |
setCookieHeaders() |
string[] |
Every Set-Cookie from every hop (see Cookies) |
toArray() |
array |
['body' => β¦, 'statusCode' => β¦, 'headers' => β¦, 'effectiveUrl' => β¦, 'setCookieHeaders' => β¦] |
dump() |
string |
Human-readable summary (for logging) |
debug() |
self |
Echo dump() and return $this |
Working with headers
Each header name maps to an array of values (string[]), because headers
like Set-Cookie legitimately appear multiple times.
[!IMPORTANT]
Set-Cookiemust not be folded into one comma-joined string (RFC 6265 Β§4.1.1), soheader('Set-Cookie')would drop all but the first cookie. UseheaderAll():
headers() contains only headers that were on the wire β the status line is not
mixed in; read it with $response->status().
Cookies
Within one request, cookies behave as they do in a browser: a cookie set on a
redirect hop (the classic POST /login β 302 + Set-Cookie β GET /home)
is sent on the follow-up automatically, on both engines. Nothing persists
between requests β there is no cookie jar yet β so to keep a session, read
the cookies every response in the chain set and send them back yourself:
Overriding a profile header
A header you pass replaces the browser profile's header of the same name
(matched case-insensitively) rather than being sent alongside it. This is how
you change the User-Agent without breaking the TLS fingerprint:
[!NOTE] Malformed headers are rejected with an
InvalidArgumentExceptionrather than silently dropped, so a typo cannot leave a header quietly unsent.
Browsers
Pass any of these names as the $browser argument (default firefox147):
All 39 supported browsers
**Chrome** β `chrome99`, `chrome99_android`, `chrome100`, `chrome101`, `chrome104`, `chrome107`, `chrome110`, `chrome116`, `chrome119`, `chrome120`, `chrome123`, `chrome124`, `chrome131`, `chrome131_android`, `chrome133a`, `chrome136`, `chrome142`, `chrome145`, `chrome146`, `chrome150` **Edge** β `edge99`, `edge101` **Firefox** β `firefox133`, `firefox135`, `firefox144`, `firefox147` *(default)* **Safari** β `safari153`, `safari155`, `safari170`, `safari172_ios`, `safari180`, `safari180_ios`, `safari184`, `safari184_ios`, `safari260`, `safari260_ios`, `safari2601` **Other** β `tor145`, `okhttp4_android`The authoritative list is always Raza\PHPImpersonate\Browser\BrowserName::getAll().
A profile is one specific browser release, and releases age: anti-bot vendors
flag versions that have been out of support for years, so a 2022 Chrome is a
signal in itself even when its handshake is byte-perfect. The profiles of
releases older than 2024 (chrome99βchrome120, edge99/edge101,
safari153βsafari172_ios) are marked @deprecated and listed in
BrowserName::DEPRECATED; they still work. For a current identity that keeps
up as profiles are added, ask for the newest of a family:
Pin the result if the identity must stay the same across releases of this package.
[!NOTE]
okhttp4_androidreproduces upstream's profile of the same name, and that profile carries a desktop Safari 17User-Agent, not an Android one β its TLS fingerprint is OkHttp's, but it will not read as an Android client. Pass your ownUser-Agentheader if you need one that matches.
Curl options
Pass extra curl options as the third constructor argument. The same curated,
validated set works on both engines β the shared registry lives in
Raza\PHPImpersonate\Support\CurlOptions:
| Option | Type | Description |
|---|---|---|
proxy |
string | Proxy address (http://, https://, socks5://, β¦) |
proxy-user |
string | Proxy credentials, user:password |
noproxy |
string | Comma-separated hosts that bypass the proxy |
referer |
string | Referer header value |
cacert |
string | Path to a custom CA bundle file |
capath |
string | Path to a custom CA directory |
max-redirs |
int β₯ 0 | Maximum redirects to follow (default 50); 0 stops at the first response |
max-filesize |
int β₯ 0 | Largest response body accepted, in bytes (default 256 MiB); a larger one throws RequestException with code 63. 0 removes the cap; a negative value is rejected |
insecure |
bool | Skip TLS certificate verification (use with care) |
[!NOTE] Any option not in this list is rejected with a clear error. Options that would alter the browser fingerprint β
ciphers,curves,tls-*, the HTTP version,user-agentβ are intentionally not configurable: overriding them would silently defeat the impersonation. Set a customUser-Agentas a request header instead β it replaces the profile's, see Overriding a profile header.[!NOTE] Responses are buffered in full before they are returned, so
max-filesizeis what stands between you and a server that streams forever. The default is deliberately finite; raise it for known-large downloads.[!TIP] Without an explicit
cacert/capath, the CA bundle is taken fromCURL_CA_BUNDLE, thenSSL_CERT_FILE, then the usual system locations (SSL_CERT_DIRsupplies a CA directory). Setting one of those variables overrides the system bundle on both engines.
Request bodies
For POST, PUT and PATCH, the array you pass is encoded based on the
Content-Type header (matched case-insensitively):
[!NOTE]
PUTandPATCHdefault to JSON;POSTdefaults to form-encoded. An explicitContent-Typealways wins.
Nested arrays are flattened to parent[child] field names in both the
form-encoded and multipart encodings, null values are dropped and booleans
become 1/0 β so the two carry the same data and only the framing differs.
Supply your own boundary= in the Content-Type to control it; otherwise one
is generated for you. File uploads are not supported: pass a pre-encoded body
via Request if you need them.
Security considerations
This library performs no SSRF filtering. Any http/https URL is fetched:
loopback, RFC 1918 ranges, link-local addresses, cloud metadata endpoints
(169.254.169.254), decimal or hex IP spellings β and a public URL may
redirect to any of them, since redirects are followed (to http/https
only). That policy belongs in your application, which knows its network; what
the library gives you to enforce it:
- Validate the URL and resolve its host yourself before calling. A hostname
check alone is not enough (DNS rebinding can change the answer between your
check and curl's); resolving it and pinning the request to an address is
the only robust defence, and needs a proxy or
--resolve-style pinning, which this library does not yet expose. - Pass
['max-redirs' => 0]to stop at the first response and vet eachLocationyourself before following it β every hop then goes through your policy. - Check
$response->effectiveUrl()afterwards to see where a followed chain actually ended.
Environment. PHP_IMPERSONATE_LIB loads an arbitrary shared object into
the PHP process; CURL_CA_BUNDLE, SSL_CERT_FILE and SSL_CERT_DIR change
what is trusted. None of them should be settable by untrusted configuration.
FFI in web SAPIs. Setting ffi.enable=On for PHP-FPM or Apache lets any
PHP code in that SAPI call arbitrary C functions, not just this library. If
that is unacceptable, leave FFI off there β the executable engine is used
automatically β or run the library from CLI workers and queues, where FFI is
always available.
What is not taken from the environment
The executable engine runs curl with -q, so a ~/.curlrc (or
$CURL_HOME/$XDG_CONFIG_HOME) on the machine is ignored β the FFI
engine never read one, and a curlrc could otherwise add headers, a proxy or
--insecure to every request. What both engines do honour, like every
libcurl program, are the http_proxy/HTTPS_PROXY/NO_PROXY variables;
pass 'proxy' => '' to go direct regardless.
Timeouts
Error handling
The library raises two kinds of error:
RequestExceptionβ a transport failure: DNS, connection, timeout, a broken transfer, a non-loadable library.InvalidArgumentExceptionβ a caller mistake: an unusable URL, a timeout outside 1β3600, an unknown browser or engine, an unsupported curl option, data that cannot be encoded as a body.
HTTP error statuses do not throw β check isSuccess().
Both implement PHPImpersonateException, so one catch covers everything the
library can raise. Reach for that unless you want to tell the two apart:
Catching RequestException alone is not enough to be safe against bad
input: InvalidArgumentException extends LogicException, so the two share no
built-in parent, and PHPImpersonate::get('not a url') would escape it.
Keeping up to date
New browser profiles and binaries come straight from upstream curl-impersonate β no manual editing:
See scripts/README.md for per-step commands and details.
Testing
The behaviour tests talk to an httpbin instance. For a fast, reliable, offline run, start one locally instead of hitting the public service:
Any test whose service is unreachable skips rather than fails, so an outage
never breaks the suite. The TLS-fingerprint tests use an external service
(tls.peet.ws, override with TLS_FINGERPRINT_URL) since httpbin can't report
JA3/JA4; they skip too when it's unavailable.
Two environment variables change that, both used by CI:
| Variable | Effect |
|---|---|
REQUIRE_LIVE_SERVICES=1 |
An unreachable service fails instead of skipping. Set it where a silent skip would be mistaken for a pass. |
PHP_IMPERSONATE_BUILD_FIXTURES=1 |
Runs the tests that compile small C fixtures with gcc to reproduce the symbol-interposition bug. Skipped by default, since not every machine has a compiler. |
Contributors changing how a profile reaches the wire should read
docs/CONFIGURATION_BASED_APPROACH.md
first: BrowserConfig is not what goes on the wire for a built-in browser,
which catches most people out once.
License
MIT β see LICENSE.md. The bundled curl-impersonate binaries
are built from curl, BoringSSL, nghttp2, brotli, zstd and libidn2, each under
its own licence; see THIRD_PARTY_NOTICES.md, which
also explains how every bundled file can be re-derived from upstream's release
(bin/UPSTREAM-CHECKSUMS).