Navicat Premium ships a small PHP script (ntunnel_mysql.php, plus
ntunnel_pgsql.php/ntunnel_sqlite.php siblings) that database
administrators drop on a web host to proxy a database connection over
plain HTTP, for environments where the real database port isn't directly
reachable. php-navicat is a native client for that same protocol,
reverse-engineered directly from Navicat's own server scripts.
It exists to move the network + binary-parsing core of that client out of
PHP userland and into C — both for raw performance, and to make it
consumable from php-wasm-compiler's
php.wasm build as a mode: static extension, reusing the libcurl
already linked into that build's core.
Part of the Kirigami project ecosystem.
Native build verified end-to-end for all three backends (mysql, pgsql,
sqlite) against real databases and Navicat's own tunnel scripts. Statically
linked into
@kirigami/php-wasm,
where it loads but hasn't been exercised against a tunnel yet. See
docs/PROTOCOL.md for the wire protocol,
docs/DECISIONS.md for the decisions, and
docs/STATUS.md for what's verified.
Deliberately procedural, not a class — a connection is a plain PHP resource wrapping one reused curl handle.
navicat_connect(
string $url,
string $host,
int $port,
string $user,
string $password,
string $db = '',
array $options = []
): resource|falseOpens a connection and validates it against the tunnel (the protocol's
actn=C action). $url is the tunnel script's own URL (e.g.
https://example.com/ntunnel_mysql.php). $options accepts charset
(default utf8), base64 (default true), timeout (default 600),
conntimeout (default 30), and proxy.
navicat_pg_connect(
string $url,
string $host,
int $port,
string $user,
string $password,
string $db = '',
array $options = []
): resource|falseSame signature, same options, and the same wire protocol as
navicat_connect() — Navicat's ntunnel_pgsql.php takes the exact same
POST fields as ntunnel_mysql.php (confirmed by reading both directly).
Only the URL you point $url at (your own ntunnel_pgsql.php) actually
selects the backend. $db defaults to Postgres' own template1 on the
server side when left empty.
navicat_sqlite_connect(
string $url,
string $dbfile,
array $options = []
): resource|falseConnects to a SQLite database file (a server-side path, resolved by the
tunnel script — no host/port/credentials). By default ($options without
create) this tests/uses an existing file, auto-detected server-side as
SQLite2 or SQLite3 from its own header bytes. Pass $options['create'] = 'sqlite2' or 'sqlite3' to instead create a brand-new database file (only
valid when $dbfile doesn't already exist). $options also accepts
base64, timeout, conntimeout, and proxy (no charset — sqlite has
no connection-charset concept, see navicat_query()).
navicat_connection_info(resource $connection): array|falseReturns ['host' => ..., 'proto' => ..., 'version' => ...] as reported by
the remote database, captured at navicat_connect() time.
navicat_query(resource $connection, string $query): array|falseRuns one query and returns its resultset: status, errno, errmsg,
affectrows, insertid, numfields, numrows, and — when
numfields > 0 — fields (per-column name/table/type/flags/length),
fieldnames, rows (each a fieldname => value associative array), and,
for navicat_sqlite_connect() connections only, valuetypes — a
row-major array (parallel to rows) of each value's real SQLite type
code. SQLite has per-value dynamic typing, so — unlike mysql/pgsql — its
fields[] header type is a useless static placeholder; valuetypes is
the only place a sqlite result's real per-value type is available.
For mysql/pgsql connections, this always runs an extra SET NAMES '<charset>' ahead of your query and discards its resultset — the tunnel
has no independent "set the connection charset" action. sqlite connections
never send this (no such statement, no connection-charset concept there).
navicat_multi_query(resource $connection, array $queries): array|falseRuns several queries in one request; returns an array of resultsets (same shape as above), one per query, in order.
navicat_escape(mixed $value): mixedEscapes a string for safe interpolation into a query sent over the
tunnel (\, NUL, \n, \r, ', ", \x1a); applied recursively to
arrays. Non-string, non-array, and empty-string values pass through
unchanged.
navicat_last_error(resource $connection): ?stringThe connection's last transport- or protocol-level error message, or
null if the last operation succeeded.
navicat_close(resource $connection): voidCloses the connection explicitly. Optional — a connection also closes when its resource is garbage-collected.
Native build (fast iteration, no Docker/Emscripten):
phpize
./configure --with-navicat
make--with-navicat (bare) finds the system libcurl via pkg-config. Pass
--with-navicat=DIR to point at a pre-built libcurl elsewhere instead
(e.g. php-wasm-compiler's own /root/lib convention).
- PHP
>= 8.5 - A C compiler,
phpize libcurl(development headers, for the native build)
- Navicat Premium —
ships the
ntunnel_*.phpserver scripts this extension talks to - php-wasm-compiler —
builds the
php.wasmruntime this extension will target as amode: staticextension
GPL-2.0-or-later. See LICENSE for the full text.
Maxime Larrivée-Roy