Search by

parasoled / scfg-php

parasoled

A parser and serialiser for scfg, the configuration format, with heredocs and comment-preserving round trips.

Package info

git.sr.ht/~parasoled/scfg-php

pkg:composer/parasoled/scfg-php

Statistics

Installs: 78

Dependents: 0

Suggesters: 0

v0.1.0 2026-10-29 12:10 UTC

This package is not auto-updated.

Last update: 2026-10-31 06:29:32 UTC


README

scfg-php reads and writes scfg, a small line-based configuration format, in PHP. It keeps comments, blank lines, quoting and empty blocks, so a file can be read, edited and written back without being rewritten. It supports heredocs for multiline values.

It conforms to version 0.3.0 of the SCFG specification, and passes every test case that version publishes. ANNEX.md is the implementation annex the specification asks for: how this library names the model, how it reports errors, and where it departs from the document.

Requirements

PHP 8.5 or later. The library has no runtime dependencies.

Installation

composer require parasoled/scfg-php

Reading a configuration

Parse a string with parse():

use SCFG\Parser;

$config = new Parser()->parse(<<<'SCFG'
    server "main" {
        port 8080
    }
    SCFG);

$port = $config->firstDirectiveNamed('server')?->block?->firstChildNamed('port');
echo $port?->paramValues()[0]; // 8080

Or read a file with parseFile():

$config = new Parser()->parseFile('config.scfg');

foreach ($config->directivesNamed('server') as $server) {
    $port = $server->block?->firstChildNamed('port');
    echo $server->paramValues()[0] ?? '', ': ', $port?->paramValues()[0] ?? '', "\n";
}

parseStream() reads an open stream. Input that isn't valid scfg throws ParseFailed. Its message puts the position first, as in 3:17: Unterminated heredoc.

Editing and writing it back

The model is immutable. Each with method returns a copy with one field changed, and leaves the original as it was.

use SCFG\Directive;
use SCFG\Param;
use SCFG\Serialiser;

$newPort = fn (Directive $child): Directive => $child->name === 'port'
    ? $child->withParams([new Param('9090')])
    : $child;

$serverWithNewPort = fn (Directive $directive): Directive => $directive->name === 'server' && $directive->block !== null
    ? $directive->withBlock($directive->block->withChildren(array_map($newPort, $directive->block->children)))
    : $directive;

$edited = $config->withDirectives(array_map($serverWithNewPort, $config->directives));
file_put_contents('config.scfg', new Serialiser()->serialise($edited));

This sets the port of every server to 9090. Every line the edit did not touch comes back as it was written, comments included.

Heredocs

A parameter may span several lines as a heredoc, in one of two modes.

Opened byModeWhat the value holds
<<<StrippedThe lines with their shared indentation removed
<<<\|LiteralThe lines exactly as written, whitespace and all
server {
    startup <<<
        systemctl start app
    >>>

    motd <<<|
  No unauthorised access
    >>>
}

A parameter's syntax says which it was, as ParamSyntax::Stripped or ParamSyntax::Literal. The value ends in the line feed of its last line.

What the serialiser guarantees

Text from Serialiser::serialise() parses back to a model equal to the one given, with quote style aside, and serialising that again gives the same text. A model with no such text throws SerialisationRefused and returns nothing. Only a model built by hand can be refused, for example a word holding a line feed. The exception's reason names which of the specification's limitations the model reached.

Development

CommandEffect
composer testRun every test
composer test:conformanceCheck the library against the specification
composer test:unitTest the library's own API
composer psalmRun Psalm at its strictest settings
composer checkPsalm and every test, as a change must pass

The conformance suite, in tests/Conformance/, checks what the specification requires:

  • its published test cases, copied unchanged into specification-0.3.0-cases.json
  • both round-trip claims, over known and generated input
  • what its prose says about particular inputs
  • its serialisation rules, and the models it says can't be written
  • its definition of equality

Licence

MIT. The full text is in LICENSE.