parasoled / scfg-php
A parser and serialiser for scfg, the configuration format, with heredocs and comment-preserving round trips.
Requires
- php: >=8.5
Requires (Dev)
- phpunit/phpunit: ^13.4
- psalm/plugin-phpunit: ^0.19.7
- vimeo/psalm: ^6.20
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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 by | Mode | What the value holds |
|---|---|---|
<<< | Stripped | The lines with their shared indentation removed |
<<<\| | Literal | The 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
| Command | Effect |
|---|---|
composer test | Run every test |
composer test:conformance | Check the library against the specification |
composer test:unit | Test the library's own API |
composer psalm | Run Psalm at its strictest settings |
composer check | Psalm 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.