Weave Code
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

Type Info Laravel Package

symfony/type-info

Symfony TypeInfo extracts and normalizes PHP type information. Resolve types from reflections or strings, build complex types via factories (nullable, list, generic, enum), cast to readable strings, and query identifiers/conditions for safer tooling and analysis.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

  1. Installation:

    composer require symfony/type-info phpstan/phpdoc-parser
    
    • phpstan/phpdoc-parser is required for resolving raw string types (e.g., @var annotations).
  2. First Use Case: Resolve a property type from a reflection object:

    use Symfony\Component\TypeInfo\TypeResolver;
    
    $resolver = TypeResolver::create();
    $type = $resolver->resolve(new \ReflectionProperty(User::class, 'email'));
    echo (string) $type; // Outputs: "string|null"
    
  3. Key Entry Points:

    • TypeResolver: Core class for resolving types from reflections or strings.
    • Type factories: Static methods like Type::object(), Type::list(), or Type::nullable() to build types programmatically.
    • Type inspection: Methods like isIdentifiedBy(), isSatisfiedBy(), or getClassName() to validate or extract type details.

Implementation Patterns

1. Type Resolution Workflows

Resolving from Reflections

// Resolve a property type
$property = new \ReflectionProperty(User::class, 'createdAt');
$type = $resolver->resolve($property); // Returns `DateTimeInterface` Type

// Resolve a method parameter
$parameter = new \ReflectionParameter([UserRepository::class, 'find'], 0);
$type = $resolver->resolve($parameter); // Returns `int` Type

Resolving from Strings

// Raw type strings (e.g., from PHPDoc)
$type = $resolver->resolve('array<int, string>'); // Returns `array<int, string>` Type

// PHPDoc annotations
$type = $resolver->resolve('@var \Illuminate\Support\Collection<int>'); // Returns `Collection<int>` Type

Caching Resolved Types

use Symfony\Component\TypeInfo\TypeContextFactory;

// Create a cached factory (persists resolved types)
$contextFactory = TypeContextFactory::create();
$context = $contextFactory->create();
$resolver = TypeResolver::create($context);

// Subsequent resolutions reuse cached results
$type1 = $resolver->resolve(new \ReflectionProperty(User::class, 'name'));
$type2 = $resolver->resolve(new \ReflectionProperty(User::class, 'name'));
// $type1 === $type2 (same instance due to caching)

2. Building Complex Types

Composing Types

// Nullable list of objects
$type = Type::list(Type::nullable(Type::object(User::class)));

// Generic collections
$type = Type::generic(Type::object(Collection::class), Type::int());

// Union types
$type = Type::union(Type::string(), Type::null());

Array Shapes (Structured Arrays)

use Symfony\Component\TypeInfo\TypeFactoryTrait;

// Define a sealed array shape
$shape = Type::arrayShape([
    'id' => Type::int(),
    'name' => Type::string(),
], sealed: true);

// Check if a type matches the shape
$shape->isSatisfiedBy(fn (Type $type) => $type->isArrayShape());

3. Integration with Laravel

Validation Rules

use Illuminate\Validation\Rule;
use Symfony\Component\TypeInfo\TypeResolver;

class CustomRule extends Rule
{
    public function __construct(private TypeResolver $resolver)
    {
    }

    public function passes($attribute, $value)
    {
        $type = $this->resolver->resolve(new \ReflectionProperty($this->getTargetClass(), $attribute));
        return $type->isIdentifiedBy(TypeIdentifier::STRING);
    }
}

Dynamic Schema Generation (OpenAPI)

use Symfony\Component\TypeInfo\Type;
use Zircote\Swagger\Scan\ClassAnalyzer;

class OpenApiGenerator
{
    public function generateSchema(string $class): array
    {
        $reflection = new \ReflectionClass($class);
        $properties = [];

        foreach ($reflection->getProperties() as $property) {
            $type = TypeResolver::create()->resolve($property);
            $properties[$property->getName()] = (string) $type;
        }

        return ['properties' => $properties];
    }
}

Type-Aware Serialization

use Symfony\Component\Serializer\Normalizer\ContextAwareNormalizerInterface;
use Symfony\Component\TypeInfo\TypeResolver;

class TypeAwareNormalizer implements ContextAwareNormalizerInterface
{
    public function __construct(private TypeResolver $resolver)
    {
    }

    public function normalize($object, string $format = null, array $context = [])
    {
        $reflection = new \ReflectionClass($object);
        $data = [];

        foreach ($reflection->getProperties() as $property) {
            $type = $this->resolver->resolve($property);
            $data[$property->getName()] = $this->normalizeValue($property->getValue($object), $type);
        }

        return $data;
    }
}

4. PHPDoc Annotation Resolution

// Resolve a PHPDoc type (e.g., from a method return)
$type = $resolver->resolve('/**
 * @return \Illuminate\Database\Eloquent\Collection<int, \App\Models\User>
 */');

// Check if a type satisfies a condition (e.g., for validation)
$type->isSatisfiedBy(fn (Type $t) =>
    $t->isIdentifiedBy(Collection::class) &&
    $t->getGenericTypes()[0]->isIdentifiedBy(TypeIdentifier::INT)
);

Gotchas and Tips

Pitfalls

  1. Namespace Resolution Issues:

    • If resolving types for classes in different namespaces, ensure the TypeContext includes all relevant autoload paths.
    • Fix: Use TypeContextFactory::create() with custom paths:
      $context = TypeContextFactory::create()
          ->addPaths([base_path('app')])
          ->create();
      
  2. BackedEnum vs. Interface Confusion:

    • The package may incorrectly treat interfaces extending BackedEnum as enums.
    • Fix: Use Type::enum() explicitly or update to v7.3.3+ (includes a fix for this).
  3. Union Type Merging:

    • Merging union types with nullable unions can lead to unexpected results (e.g., Type::union(Type::int(), Type::null())).
    • Fix: Use TypeFactoryTrait::union() carefully or normalize types first:
      $union = Type::union(Type::int(), Type::null());
      $normalized = $union->normalize(); // Ensures consistent structure
      
  4. PHPDoc Parser Dependencies:

    • If using @var annotations, ensure phpstan/phpdoc-parser is installed and compatible with your PHP version.
    • Fix: Pin the version in composer.json:
      "require": {
          "phpstan/phpdoc-parser": "^1.15"
      }
      
  5. Caching Overhead:

    • Caching resolved types (TypeContextFactory) improves performance but may consume memory for large codebases.
    • Tip: Disable caching in development:
      $context = TypeContextFactory::create()->disableCache()->create();
      

Debugging Tips

  1. Inspect Type Structures: Use (string) $type to debug complex types:

    $type = Type::generic(Type::object(Collection::class), Type::int());
    echo (string) $type; // Outputs: "Collection<int>"
    
  2. Check Type Identifiers: Use isIdentifiedBy() to verify type matches:

    $type->isIdentifiedBy(TypeIdentifier::OBJECT, User::class);
    // or
    $type->isIdentifiedBy('array');
    
  3. Validate with isSatisfiedBy: Custom predicates for complex conditions:

    $type->isSatisfiedBy(fn (Type $t) =>
        $t->isList() && $t->getInnerType()->isIdentifiedBy(TypeIdentifier::STRING)
    );
    
  4. Handle Generic Type Errors: If generics fail to resolve (e.g., missing type arguments), wrap in a fallback:

    try {
        $type = $resolver->resolve(new \ReflectionProperty($class, 'genericProp'));
    } catch (\InvalidArgumentException $e) {
        $type = Type::mixed(); // Fallback
    }
    

Extension Points

  1. Custom Type Resolvers: Extend TypeResolver to support domain-specific types (e.g., Laravel collections):
    use Symfony\Component\TypeInfo\TypeResolverInterface;
    
    class LaravelTypeResolver implements TypeResolverInterface
    {
        public function resolve($subject): Type
        {
            if ($subject instanceof \ReflectionProperty && $subject->getType()?->getName() === 'Illuminate\Support\Collection') {
                return Type::generic(Type::object
    
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.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
milesj/emojibase
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport