rmb32 / typed-input
Typed readers for untyped input — decoded JSON and Symfony Console arguments/options — that fail with a clear message instead of leaking mixed
Requires
- php: ^8.5
- symfony/console: ^7.0
Requires (Dev)
- phpstan/phpstan: ^2.2
- phpunit/phpunit: ^12.0
- squizlabs/php_codesniffer: ^4.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-27 06:16:51 UTC
README
Read untyped input as the types your code expects, with an error message that names the exact value that was wrong.
json_decode() hands you mixed, and so do Symfony Console's getArgument() and getOption().
Checking every value by hand is tedious, and it's easy to skip a check. Typed Input reads each value
through a typed accessor: you get a string, a list<string> or a nested object, or an
InvalidValueException that tells you where the bad value is:
Expected a non-empty string for 'deploy.json.services.worker.image'
Requirements
- PHP 8.5+
- Symfony Console 7 (installed with the package; only
ConsoleInputuses it)
Installation
composer require rmb32/typed-input
Reading decoded JSON
Take a deployment config like this one:
{
"name": "shop",
"domains": ["shop.example.com", "www.shop.example.com"],
"env": { "APP_ENV": "prod", "LOG_LEVEL": "warning" },
"services": {
"web": { "image": "nginx:1.27" },
"worker": { "image": "" }
},
"steps": [{ "run": "composer install" }, { "run": "composer test" }]
}
Wrap the decoded data in a Payload, giving it a label that says where the data came from. The
label starts every error path.
use Rmb32\TypedInput\Api\InvalidValueException;
use Rmb32\TypedInput\Api\Payload;
$config = Payload::from(json_decode($json, true), 'deploy.json');
$config->string('name'); // 'shop'
$config->optionalString('notes'); // null, because the key is absent
$config->stringList('domains'); // ['shop.example.com', 'www.shop.example.com']
$config->stringList('tags'); // [], because an absent list reads as empty
$config->stringMap('env'); // ['APP_ENV' => 'prod', 'LOG_LEVEL' => 'warning']
foreach ($config->items('steps') as $step) { // a JSON array of objects
$step->string('run'); // 'composer install', then 'composer test'
}
try {
foreach ($config->payload('services')->entries() as $serviceName => $service) {
$service->string('image'); // 'nginx:1.27', then it throws for 'worker'
}
} catch (InvalidValueException $e) {
echo $e->getMessage(); // Expected a non-empty string for 'deploy.json.services.worker.image'
}
| Method | Returns | When the key is absent |
|---|---|---|
string($key) | a non-empty string | throws |
optionalString($key) | ?string | null |
stringList($key) | list<string> | [] |
stringMap($key) | array<string,string> | [] |
payload($key, absentIsEmpty: false) | a nested Payload (a JSON object) | throws, or an empty Payload when absentIsEmpty is true |
items($key) | list<Payload> (a JSON array of objects) | [] |
entries() | each value of this object as a Payload, keyed by its key as a string | — |
raw($key) | the value as mixed, for a shape the typed methods don't cover | null |
toArray() | the wrapped array | — |
An empty string is never accepted: string(), optionalString(), stringList() and stringMap()
all throw on "".
Reading a console command's input
use Rmb32\TypedInput\Api\ConsoleInput;
protected function execute(InputInterface $input, OutputInterface $output): int
{
$console = new ConsoleInput($input);
$environment = $console->argument('environment'); // string, or it throws
$tag = $console->optionalOption('tag'); // ?string, null when --tag isn't given
// ...
}
A missing required value fails with Expected a non-empty string for 'argument environment' (or
'option --tag'). optionalArgument() and option() complete the set.
Used in Barn
This package is used in Barn. Learn about Barn.
More docs
Internals · History · Known issues
License
Proprietary. See LICENSE. Copyright (c) Roger Barnfather.