Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Phpdoc Reader Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer (no framework integration required, zero runtime dependencies):

composer require php-di/phpdoc-reader

The entry point is a single class, PhpDocReader\PhpDocReader. It takes a ReflectionProperty or ReflectionParameter and returns the resolved fully-qualified class name as a string, or null when the annotation is absent or is a primitive type.

use PhpDocReader\PhpDocReader;

$reader = new PhpDocReader();

// @var on a property
$property = new ReflectionProperty(Cache::class, 'backend');
$type = $reader->getPropertyClass($property); // "My\Cache\Backend" or null

// @param on a constructor/method parameter
$param = new ReflectionParameter([Cache::class, '__construct'], 'backend');
$type = $reader->getParameterClass($param); // "My\Cache\Backend" or null

Where to look first:

  1. The README's "Features" section — it is the whole API surface.
  2. PhpDocReader::getPropertyClass() and getParameterClass() in src/PhpDocReader.php — read the regex/resolution logic once so you know exactly what it does and does not match.
  3. The tests/ directory — it is the best specification of import-resolution edge cases (aliases, relative names, fully-qualified names).

The canonical first use case is container autowiring: you have a constructor parameter with no native type hint, but a docblock says @param Backend $backend. Call getParameterClass() to recover My\Cache\Backend, then recursively resolve that class from the container.

Implementation Patterns

Wrap it behind your own type resolver. Do not sprinkle PhpDocReader calls through your codebase. Define an interface like TypeResolver::resolveParameter(ReflectionParameter): ?string and implement it with this package. That keeps the 2020-era dependency swappable and lets you layer native reflection types on top.

Native types first, phpdoc second. The reader knows nothing about PHP 7.4+ typed properties or PHP 8 union types. Your resolver should check $parameter->getType() / $property->getType() first and only fall back to phpdoc when there is no native type. This is exactly the strategy PHP-DI itself uses.

Cache aggressively. Reflection plus docblock parsing is not free, and you will call this once per constructor parameter on every container build. Memoize by a stable key such as $declaringClass . '::' . $memberName:

final class CachedTypeResolver implements TypeResolver
{
    private array $cache = [];

    public function __construct(private PhpDocReader $reader) {}

    public function resolveParameter(ReflectionParameter $p): ?string
    {
        $key = $p->getDeclaringClass()->getName() . '::' . $p->getName();

        return $this->cache[$key] ??= $this->reader->getParameterClass($p);
    }
}

Autowiring container integration. For each class you instantiate, loop over __construct parameters: use native type if present, otherwise getParameterClass(), otherwise throw a "cannot resolve" error. Then recurse into the container.

public function resolve(string $class): object
{
    $ref = new ReflectionClass($class);
    $ctor = $ref->getConstructor();
    if ($ctor === null) {
        return $ref->newInstance();
    }

    $args = [];
    foreach ($ctor->getParameters() as $param) {
        $type = $this->typeResolver->resolveParameter($param);

        if ($type === null) {
            if ($param->isDefaultValueAvailable()) {
                $args[] = $param->getDefaultValue();
                continue;
            }
            throw new RuntimeException("Cannot autowire \${$param->getName()} of $class");
        }
        $args[] = $this->resolve($type);
    }

    return $ref->newInstanceArgs($args);
}

Property injection. For setter-less property injection, iterate getProperties(), call getPropertyClass(), and assign via reflection (setAccessible(true) is a no-op on PHP 8.1+, still needed on older versions). This pattern shows up in Zen-style containers.

Prefer the declaring class for inherited members. ReflectionProperty::getDeclaringClass() points at the class that declared the docblock, and therefore at the file whose use statements matter. Always let the reader resolve against $property->getDeclaringClass()'s file — which it does internally — rather than re-resolving imports yourself.

Testing pattern. Build fixture classes in tests/Fixtures/ covering each resolution rule (FQCN, imported, relative, aliased) and assert on the string output. Keep these tests when you wrap the reader so an upstream swap is provably safe.

Gotchas and Tips

  • Primitives return null, not a string. @var string, @var int[], @var array all yield null. Treat null as "unknown", not as "no dependency".
  • No generic support. @var Collection<Foo> resolves to Collection, dropping the type argument. If you need generics you must parse the docblock yourself on top of the raw text.
  • Unions are unreliable. @var Foo|Bar, nullable @var ?Foo, and @var Foo|null predate PHP 8 and are not modelled. Do not rely on the reader for union types; prefer native reflection, or normalize the docblock yourself before passing it through.
  • The reader does not verify the class exists. It returns a resolved name from the docblock's namespace context. If the docblock is stale (class moved/renamed), you get a name that class_exists() will reject. Validate downstream and fail with a message that quotes the original annotation.
  • Resolution mirrors PHP import rules. FQCN (\Foo\Bar), use imports, aliases (use Foo as Bar), and relative names (Sub\Class) all work — but only within the declaring file. Grouped use statements and PHP 8.0+ constructor-promoted property docblocks are not explicitly handled; test your PHP version.
  • PHP 8 attributes are invisible to this package. If your team has migrated to #[Inject] style metadata, this reader is the wrong tool. Treat it as legacy phpdoc-only infrastructure.
  • Last release was 2020. PHP 8.0–8.4 emit deprecation notices only if the internals trip them; the package itself is stable but effectively maintenance-only. Pin ^1.6 and keep a one-class adapter in front so you can fork or replace it without touching call sites.
  • No built-in cache. Every call re-reads the docblock. In a long-running worker (Octane, RoadRunner, Swoole) cache results once at boot; in classic FPM, a lightweight array cache per request is usually enough.
  • Debugging tip: when a type resolves unexpectedly, dump (new ReflectionProperty($class, $prop))->getDocComment() and the use lines from (new ReflectionClass($class))->getFileName(). Nine out of ten surprises are a missing use statement or a docblock written in a different file than the declaring class.
  • Extension point: PhpDocReader is not final and has no interface, so subclassing to override resolution is possible but brittle (private helpers). Prefer composition and a custom resolver interface over inheritance.
  • Guard the file-read cost. For properties inherited from vendor classes, the reader still loads the declaring file. Batch-resolve all constructor parameters for a class once, then cache, rather than resolving lazily mid-request.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo