Languages: English · Русский · 简体中文
Playground: convert JSON / YAML / TOML / INI ⇄ Ktav in your browser at ktav-lang.github.io.
PHP bindings for the Ktav configuration format. Thin wrapper around the reference Rust parser, loaded at runtime through the PHP FFI extension — no PHP extension to compile, no PECL install. Plain Composer dependency, the native binary is fetched on first call.
Requires PHP 7.4+ with ext-ffi enabled (default in CLI; web SAPIs
need ffi.enable=1 in php.ini).
composer require ktav-lang/ktavuse Ktav\Ktav;
$src = <<<KTAV
service: web
port: 8080
ratio: 0.75
tls: true
tags: [
prod
eu-west-1
]
db.host: primary.internal
db.timeout: 30
KTAV;
$cfg = Ktav::loads($src);
$service = $cfg['service']; // string
$port = $cfg['port']; // int
$ratio = $cfg['ratio']; // float
$tls = $cfg['tls']; // bool
$tags = $cfg['tags']; // array<string>
$dbHost = $cfg['db']['host']; // string
$dbTimeout = $cfg['db']['timeout']; // int$doc = [
'name' => 'frontend',
'port' => 8443,
'tls' => true,
'ratio' => 0.95,
'upstreams' => [
['host' => 'a.example', 'port' => 1080],
['host' => 'b.example', 'port' => 1080],
],
'notes' => null,
];
$text = Ktav::dumps($doc);A complete runnable example lives in examples/basic.php.
| Method | Purpose |
|---|---|
Ktav::loads(string $src): mixed |
Parse a Ktav document. |
Ktav::loadsStrict(string $src): mixed |
Parse with strict numeric spelling checks. |
Ktav::dumps(array $value): string |
Render an associative array as Ktav text. |
Ktav::format(string $src): string |
Normalise a document's spelling, keeping comments. |
Ktav::nativeVersion(): string |
Version of the loaded ktav_cabi. |
Ktav::format() takes Ktav source text and returns Ktav source
text. It normalises structure to canonical form (§ 5.9) while keeping
the trivia the canonical writer drops:
echo Ktav::format("## the server\nserver: {host: a, port: 80}\n");
// ## the server
// server: {
// host: a
// port: 80
// }Every comment survives verbatim — Ktav has no trailing comments (§ 3.4: a comment owns a whole line), so attachment is unambiguous. Blank lines survive as a grouping hint, but a run of two or more collapses to exactly one and blank padding just inside a bracket is dropped, which makes the transform a fixed point: formatting already-formatted text changes nothing. Key order is never changed — canonical form has no sorting rule, and reordering keys would make review diffs worse.
KtavException is thrown on any parse or render failure. Beyond a
human-readable getMessage(), it carries the nine structured fields of
the core's error envelope:
try {
Ktav::loadsStrict("a: 1.10\n");
} catch (KtavException $e) {
$e->getError(); // "LossyScalar"
$e->getBody(); // "1.10" — as written
$e->getCanonical(); // "1.1" — as it would be stored
$e->getSpecSection(); // "§3.6/§5.2"
$e->getSpan(); // ["start" => 0, "end" => 7]
}The full set is getError(), getReason(), getErrorLine(),
getLineText(), getSpan(), getPath(), getBody(),
getCanonical(), getSpecSection(). Absent information is null,
never a missing accessor, so a caller can read any field without
checking the error class first.
getPath() is an array of exact decoded key segments, never a joined
string: a key literally named a.b is one segment and cannot be
confused with a two-segment path.
Two writer rejections are named apart — "UnrepresentableAt" when the
writer can say which node is at fault (it fills getPath() too), and
"Unrepresentable" when it cannot. The reason code is the same in
both, so matching on getReason() is enough when you only need to know
that a write was refused.
getErrorLine() rather than getLine(), because PHP declares
Exception::getLine() final.
| Ktav | PHP |
|---|---|
null |
null |
true / false |
bool |
| bare integer | int if it fits, else string (PHP has no native bigint — wrap your own GMP / BCMath if you need arithmetic). |
| bare decimal | float |
| other scalar | string |
[ ... ] |
sequential array |
{ ... } |
associative array (insertion order preserved) |
To emit an arbitrary-precision integer, wrap the digit string yourself:
['big' => ['$i' => '9999999999999999999']] — same envelope used on the
wire between PHP and the native side.
Since spec 0.6.4 a literal . or : inside a key segment is written
with a backslash:
a\.b: v # key is the single segment "a.b" → ["a.b" => "v"]
a\:b: v # key contains a colon → ["a:b" => "v"]
x.y\.z: v # split on the first dot only → ["x" => ["y.z" => "v"]]
A literal backslash in a key is \\.
On first call:
KTAV_LIB_PATHenv var, if set.- User cache —
<userCache>/ktav-php/v<version>/<asset>, downloaded on a previous call. - GitHub Release download — fetched once from
github.com/ktav-lang/php/releases/download/v<version>/<asset>and cached under (2). Requires network on first call after install.
<userCache> is %LOCALAPPDATA% on Windows, ~/Library/Caches on
macOS, $XDG_CACHE_HOME or ~/.cache on Linux.
- PHP 7.4 / 8.0 / 8.1 / 8.2 / 8.3+. Tested on the LTS lines on every CI run.
- Prebuilt binaries for:
linux/amd64,linux/arm64,darwin/amd64,darwin/arm64,windows/amd64,windows/arm64. - Linux distros must use glibc 2.17+ (zigbuild baseline). Alpine (musl) support is planned.
MIT OR Apache-2.0 — see LICENSE-MIT and LICENSE-APACHE.
spec— specification + conformance suiterust— reference Rust crate (cargo add ktav)csharp— C# / .NET (dotnet add package Ktav)golang— Go (go get github.com/ktav-lang/golang)java— Java / JVM (io.github.ktav-lang:ktavon Maven Central)js— JS / TS (npm install @ktav-lang/ktav)python— Python (pip install ktav)