Search by

rmb32 / typed-input

rogerbarnfather

Typed readers for untyped input — decoded JSON and Symfony Console arguments/options — that fail with a clear message instead of leaking mixed

Package info

bitbucket.org/rmb32/typed-input

pkg:composer/rmb32/typed-input

Statistics

Installs: 195

Dependents: 8

Suggesters: 0

v1.0.1 2026-10-08 18:26 UTC

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 ConsoleInput uses 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'
}
MethodReturnsWhen the key is absent
string($key)a non-empty stringthrows
optionalString($key)?stringnull
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 covernull
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.