Get started / PHP (Symfony)
docuconf for PHP (Symfony)
Declare the variables in config/packages/docuconf.yaml, written as in the contract, and read them with the docuconf env processor instead of int: and bool:, or from the Docuconf\Values service. The kernel checks them all at boot.
- Builds on
- Symfony config + %env()% processors
- Package
- docuconf/docuconf (Packagist)
- Requires
- PHP 8.2, Symfony 6.4 to 8
- Status
- v0.1 alpha, not yet released · conformance 134 of 134
- Source
- docuconf-php · the orders example
Not published yet
main branch. After the first release it will be composer require docuconf/docuconf.The code below is the orders service, the example every SDK ships: six variables, one of them a secret, checked the same way in every language. Compare it across languages.
1.Install
The package is not on Packagist yet. Install it from GitHub (PHP 8.2+, with mbstring), then register Docuconf\Symfony\DocuconfBundle in config/bundles.php: Flex does not register it yet.
composer config repositories.docuconf vcs https://github.com/Docuconf/docuconf-php
composer require docuconf/docuconf:dev-main2.Declare your configuration
The variables, in Symfony config, as they appear in the contract. Symfony checks the keys itself, so a typo such as secert: true is an Unrecognized option error when the container compiles. %env(docuconf:PORT)% reads the checked, typed value.
# Every variable the service reads, written as in contract.cue. Read them
# with %env(docuconf:NAME)%: the value docuconf validated, typed.
docuconf:
name: orders-symfony
vars:
PORT: {type: int, description: HTTP listen port, min: 1, max: 65535, default: 8080}
LOG_LEVEL: {type: enum, description: Minimum log level, values: [debug, info, warn, error], default: info}
DATABASE_URL: {type: url, description: Orders database connection string, required: true, secret: true, schemes: [postgres, postgresql], maxLength: 2048}
ALLOWED_ORIGINS: {type: list, description: Origins allowed to call the API (CORS), items: string, minItems: 1, default: ['http://localhost:3000']}
REQUEST_TIMEOUT: {type: duration, description: Timeout for each request, min: 1s, max: 5m, default: 30s}
WORKER_COUNT: {type: int, description: Number of background workers, min: 1, max: 64, default: 4}
parameters:
orders.port: '%env(docuconf:PORT)%'
orders.request_timeout_seconds: '%env(docuconf_seconds:REQUEST_TIMEOUT)%'3.Load it at boot
The bundle validates when the kernel boots: on web requests, and for every console command except those in docuconf.skip_commands (cache:*, secrets:*, debug:*). Inject Docuconf\Values for the typed values; redacted() hides the secret.
// The typed configuration, with the secret shown as "***".
#[Route('/config')]
public function config(Values $config): JsonResponse
{
return new JsonResponse($config->redacted());
}4.Run it, and see an error
Run the example with a valid environment:
DATABASE_URL=postgres://orders:pw@localhost:5432/orders php -S 127.0.0.1:8080 -t publicWith PORT=0 and no DATABASE_URL, bin/console docuconf:check lists every problem with its error code and exits 1. A web request fails the same way when the kernel boots, with the report in the server log, and so does every console command that runs the app.
$ PORT=0 bin/console docuconf:check
docuconf: 2 configuration problems:
- PORT [out_of_range]: must be at least 1 (got "0")
- DATABASE_URL [missing_required]: required, but not set5.Test your configuration
A KernelTestCase gets the Docuconf service and checks an explicit environment. The kernel still validates the real environment when it boots, so the test run gets a valid one; framework.test: true provides the test container.
<?php
use Docuconf\Symfony\Docuconf;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
// check() takes an explicit environment: the process environment is not read or changed.
final class ConfigTest extends KernelTestCase
{
public function testAcceptsAValidEnvironment(): void
{
$result = self::getContainer()->get(Docuconf::class)->check(['DATABASE_URL' => 'postgres://orders@db/orders']);
self::assertSame([], $result->violations);
}
public function testReportsEveryProblem(): void
{
$result = self::getContainer()->get(Docuconf::class)->check(['PORT' => '70000']);
self::assertSame(
['PORT:out_of_range', 'DATABASE_URL:missing_required'],
array_map(fn ($v) => "{$v->input}:{$v->code}", $result->violations),
);
}
}# The test container, for KernelTestCase.
framework:
test: trueDATABASE_URL=postgres://ci@db/orders APP_ENV=test KERNEL_CLASS='App\Kernel' vendor/bin/phpunit tests/ConfigTest.php6.Export the contract
bin/console docuconf:export writes the contract from the bundle configuration. --check fails when the committed file is out of date, for CI.
bin/console docuconf:export --output=contract.cue// Code generated by docuconf. DO NOT EDIT.
package orders_symfony
import "docuconf.dev/contract"
contract.#Contract & {
apiVersion: "docuconf.dev/v1alpha1"
kind: "ConfigContract"
metadata: {
name: "orders-symfony"
generator: {
language: "php"
sdk: "docuconf/docuconf"
version: "0.1.0"
}
}
vars: {
ALLOWED_ORIGINS: {
type: "list"
description: "Origins allowed to call the API (CORS)"
items: "string"
encoding: "csv"
separator: ","
minItems: 1
default: ["http://localhost:3000"]
}
DATABASE_URL: {
type: "url"
description: "Orders database connection string"
required: true
secret: true
schemes: ["postgres", "postgresql"]
maxLength: 2048
}
LOG_LEVEL: {
type: "enum"
description: "Minimum log level"
values: ["debug", "info", "warn", "error"]
default: "info"
}
PORT: {
type: "int"
description: "HTTP listen port"
min: 1
max: 65535
default: 8080
}
REQUEST_TIMEOUT: {
type: "duration"
description: "Timeout for each request"
encoding: "go"
min: "1s"
max: "5m"
default: "30s"
}
WORKER_COUNT: {
type: "int"
description: "Number of background workers"
min: 1
max: 64
default: 4
}
}
}The platform then checks each environment's values against contract.cue before it deploys, with docuconf vet, the Helm chart or CUE. See How it works.
7.Symfony
Values are read the way %env(NAME)% reads them: real environment variables, .env files, the secrets vault (bin/console secrets:set DATABASE_URL) and env(NAME) parameter defaults. Put the processor innermost when chaining: %env(default:fallback:docuconf:PORT)%. presets: [symfony] also declares APP_SECRET, APP_ENV and APP_DEBUG, and declaration: can point at a PHP file for file inputs. See the README.
Next
- The docuconf-php README: every type, file input and option.
- The orders example, with its smoke test.
- The same service in every language, side by side.
- What every SDK supports, and this one's known gaps: No profiles or config-file overlays. No reload: watch. No Flex recipe yet: register the bundle by hand.
Every snippet on this page is compiled or run against docuconf-php's main branch in CI. Facts checked 2026-10-08.