vanio/type-parser
Vanio Type Parser is a lightweight PHP library that turns type declaration strings—unions, intersections, nullable types, generics, arrays and callables—into structured, inspectable objects, making it easy to analyze, validate and manipulate type information for reflection, tooling and code generati...
Install the package and check its compatibility with your PHP version before wiring it into anything:
composer require vanio/type-parser
Where to look first
README.md — it is the only reliable source of truth for the exact entry-point signature; the package is small and low-traffic, so the docs and the src/ directory are effectively the same specification.src/ — read the parser class and the value object it returns (usually a Type/TypeNode/array descriptor). This tells you what shapes you can expect: scalars, nullables, unions, arrays, and generics.composer.json — note the php-parser/nikic constraint. If you are on a recent PHP version, confirm the dependency tree resolves without forcing a downgrade.First use case
The canonical use is reflection-free introspection of a DTO/model property when the native type is missing or too coarse:
use Vanio\TypeParser\TypeParser;
$parser = new TypeParser();
// Typical shape — verify the method name against the README:
$type = $parser->parsePropertyType(App\Dto\CreateOrder::class, 'items');
// e.g. resolves "@var array<int, App\Dto\OrderLine> $items"
A good first integration is a small serializer/hydrator or a form generator that needs to know "is this property a collection of OrderLine, and is it nullable?" before it walks the object graph. Start with one class, dump the parsed result, and confirm it matches what you expect before applying it across a codebase.
Wrap it in a singleton service. Parsing docblocks is cheap but not free, and it is pure. Bind one instance in a service provider and inject it, rather than new TypeParser() at call sites:
$this->app->singleton(TypeParser::class, fn () => new TypeParser());
Cache per class+property. If you call the parser inside a loop (serializers, API resources, admin grids), memoize results keyed by Class::property. A simple array cache on the wrapper is enough for a request; use the Laravel cache with a tag or a file-based cache keyed by a hash of the class file's mtime if you need it across requests. Always invalidate when the source file changes in development.
Pair it with native reflection as a fallback. The most robust pattern is:
$reflection = new ReflectionProperty($class, $property);
$native = $reflection->getType();
if ($native !== null) {
// use ReflectionNamedType / ReflectionUnionType
}
$parsed = $parser->parsePropertyType($class, $property); // docblock fallback
This keeps you honest on modern codebases where most properties already have native types, and reserves the parser for the genuinely dynamic cases (collections, generics, legacy code).
Use it in a custom cast or resource layer. A Castable-style attribute or an API resource that decides how to serialize a property can consult the parsed type to choose a transformer:
$type = $parser->parsePropertyType($this->model::class, $key);
if ($type->isCollection()) {
return $value->map(fn ($item) => $this->transform($item, $type->itemType()));
}
Normalise the result into your own type object. Do not let the package's value objects leak throughout your domain. Map them into a small PropertyType VO with the handful of predicates you actually need (isNullable, isCollection, className, scalar). This makes swapping the parser out later a one-file change.
@var on the property. A @var on the class-level docblock, or one accidentally attached to a method, will not be found. Verify with ReflectionProperty::getDocComment().OrderLine relying on the file's use statements. Confirm whether the parser resolves imports to FQCNs or returns the raw string. If it returns raw strings, resolve them yourself against the file's namespace and imports (or require developers to write FQCNs — easier to enforce with a lint rule).?Foo, Foo|null, and null|Foo to be handled, but test ?array<int, Foo> specifically; nested nullability is where small parsers break. Also check how mixed and missing @var are represented — usually as null or an empty type, not an exception.array<int, Foo> and Foo[] are commonly supported, but Collection<Foo> or array{foo: string} (array shapes) likely are not. If you need shapes, do not assume support; add a fixture test for every type form you rely on.filemtime() of the class file during development, or simply disable cross-request caching outside production.nikic/php-parser majors quickly. Pin the package version in composer.json and watch for conflicts when you upgrade other tooling.ReflectionProperty::getDocComment() before blaming the parser — nine times out of ten the annotation itself is malformed or mis-indented.@var strings) rather than patching the package, which will be hard to maintain given its size.?Foo, Foo|null, Foo[], array<int, Foo>, and a missing @var will protect you from being surprised by a patch release.How can I help you explore Laravel packages today?