php-di/phpdoc-reader
PhpDocReader parses @var and @param docblock types to resolve class names, supporting fully qualified, imported, relative, and aliased names with the same rules as PHP. Primitive types are ignored; only valid class names are returned. Used by PHP-DI.
What it is: A ~300-line, single-responsibility library that takes a ReflectionProperty or ReflectionParameter, reads the associated docblock, parses @var/@param, and resolves the type token to a fully-qualified class name using PHP's own name-resolution rules (FQN, use imports, relative namespace, aliases). It returns null for primitives, scalars, arrays, and anything that isn't a resolvable class. It is explicitly not a general PHPDoc parser — no generics, no array shapes, no @method, no attribute reading.
Architecture fit: Narrow and well-bounded. The public surface is effectively two methods on one class, which makes it easy to wrap, stub, or replace. It fits cleanly as an inference adapter inside a dependency-injection container or a type-introspection layer — which is precisely its heritage: it exists to serve PHP-DI's autowiring. If our use case is "resolve constructor/property types from docblocks so a container can autowire untyped or legacy code," it is a direct fit. If our use case is broader static analysis, schema generation, or API documentation, it is underpowered and we would outgrow it immediately.
Integration feasibility: High. Composer-installable, MIT, no framework coupling, no service provider, no config. One use statement and a new PhpDocReader() and we are productive in an afternoon. The main gating check is the php constraint in composer.json versus our target runtime — the last tagged release is October 2020, so a PHP 8.3/8.4/8.5 runtime needs verification against CI rather than assumption (the repo does carry a GitHub Actions CI badge, which is a positive signal that it is not fully abandoned).
Technical risk: Moderate but contained.
| Risk | Severity | Mitigation |
|---|---|---|
Docblock reliance — opcache.save_comments=0 silently blanks every docblock, and the library degrades to "always null" rather than erroring |
High in prod | Non-negotiable ini baseline + startup assertion |
Source file must be readable and tokenizable (it parses use statements from the declaring file); eval'd, generated, or comment-stripped code fails |
Medium | Compile-time only usage; never at request time |
Heuristic scope — no generics, no unions, no ?Foo, no inline @var in method bodies |
Medium | Document supported grammar; fall back to native reflection first |
| Single-maintainer, 74 stars, no release in ~5 years, dependents signal in our data shows 0 (contradicted by the README's PHP-DI/Zen users — treat the metric as unreliable and confirm on Packagist) | Medium | MIT + tiny surface = cheap fork; wrap behind our own interface |
Returns null liberally with no diagnostic channel — failures are silent |
Medium | Consumer-side null handling discipline and instrumentation |
Key questions:
use statements (use A\{B, C}), aliased imports, and use function/use const without mis-resolving?Foo|null, ?Foo, Foo&Bar)? Return the first, return null, or throw?getPropertyClass/getParameterClass the intended stable API, or is there a newer entry point the README hasn't caught up to?phpdocumentor/reflection-docblock, roave/better-reflection, or symfony/property-info — serve us with more headroom?Stack fit: Near-perfect for a Laravel shop that already leans on container autowiring and docblock-annotated service classes. Laravel's own container resolves via reflection on parameter types; this library fills the gap only where parameters are untyped and annotated, or where we are introspecting properties for custom resolution. Important framing: Laravel does not need this to function. This is only justified if we are (a) introducing PHP-DI alongside or instead of the Laravel container, (b) building an internal package that does docblock-driven autowiring, or (c) writing tooling that must read @param/@var class references. Absent one of those, the correct recommendation is do not adopt.
Migration path: Low-friction and reversible.
composer require php-di/phpdoc-reader with an exact-pinned constraint (^1.4), committed lockfile.App\Introspection\DocblockTypeResolver, with resolvePropertyType() / resolveParameterType() returning a nullable string. Every call site depends on our interface, never on PhpDocReader directly. This is the whole exit strategy — if the package dies or proves too narrow, we swap the implementation for reflection-docblock and change one binding.class::member, populated during container/compile warm-up rather than per request.Compatibility: No Laravel service provider, facade, or config to publish — register the binding manually in a service provider. Watch three environment assumptions: docblocks must survive to runtime (opcache.save_comments=1), source files must ship with the app (rules out some build-step stripping/minification pipelines), and the package's PHP constraint must admit our runtime. All three are checkable in a single CI job.
Sequencing:
use resolution, union/nullable handling, and opcache behaviour against a representative sample of our codebase. Measure how many @param/@var annotations actually resolve to a class — if the hit rate is low, kill the initiative here.null return with the declaring class and member; this is the only signal we will have that parsing silently failed.opcache.save_comments=1 to all environment configs and a boot-time guard that fails loudly if it is disabled.Maintenance: Effectively zero ongoing cost — the library has no dependencies, no configuration, no I/O beyond reading source files, and its behaviour is defined by PHP's name-resolution rules, which do not change. The flip side is that the dependency itself has no maintenance velocity: a five-year-old last release means an aging PHP compatibility surface and a maintainer who may not respond. Our wrapper interface converts that from a risk into a swap exercise, so the maintenance burden is really "maintain a 100-line adapter," not "maintain a library."
Support: Community-only, single primary maintainer, no commercial backing. Given the tiny code size and MIT licence, the realistic support model is fork-and-patch: if a PHP 8.x deprecation or a use-statement edge case bites us, we can vendor or fork and fix in hours. Budget for that possibility rather than treating upstream as an SLA.
Scaling: Parse cost is proportional to distinct reflected members and involves file reads plus tokenization, so it does not belong on the request path. Keep it in container compilation / build warm-up and cache aggressively. At that placement, scale impact is nil. If it ever leaks into per-request hot paths under load, the symptom will be unexpected filesystem reads in profiling rather than CPU burn — watch for that.
Failure modes (ranked by likelihood of surprising us in production):
opcache.save_comments=0 — the highest-value guardrail. Everything works in dev, every docblock type vanishes in prod, and container resolution quietly falls back or throws an unhelpful "cannot resolve" error. Fail fast at boot.use group the parser doesn't handle, or a type that is really a generic (array<Foo>) and gets misread as a class named array<Foo>. Validate resolved names against class_exists before handing them to the container..php sources, or classes loaded from phar/eval contexts, cause resolution to fail or throw. Ensure container compilation happens where source is present.use statements change without the class name changing, we serve a stale FQN. Invalidate on deploy, or key on file mtime in dev.Ramp-up: Hours. Any PHP developer reads the README and is productive immediately; the API is two methods and the concept is familiar. The real onboarding cost is not the library but the policy around it — native types first, docblocks as fallback, resolve at compile time, assert opcache settings, instrument nulls. Put that in the wrapper's docblock and the team never needs to think about the underlying package again.
Verdict: Approve as a wrapped, compile-time-only, optional dependency, gated on the PHP-version check and a hit-rate spike — and only if we have a genuine docblock-driven autowiring or introspection requirement. Do not adopt it as general PHPDoc tooling; the grammar coverage is too thin and better-supported alternatives exist. Treat it as a small, replaceable adapter, not a platform commitment.
How can I help you explore Laravel packages today?