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

Property Info Laravel Package

symfony/property-info

Symfony PropertyInfo extracts metadata about PHP class properties (types, visibility, accessors) from multiple sources like reflection, PHPDoc, and serializers. Useful for building API docs, forms, validation, and other tooling that needs reliable property details.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Setup

Install via Composer:

composer require symfony/property-info

First Use Case: Type Extraction

Extract property types from a class (e.g., for validation or serialization):

use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;

$extractors = [
    new ReflectionExtractor(),
    new PhpDocExtractor(),
];

$propertyInfoExtractor = new PropertyInfoExtractor($extractors);

// Get type for a property
$type = $propertyInfoExtractor->getTypes(User::class, 'email');

Where to Look First

  • Official Documentation (API reference, extractors, and usage examples).
  • PropertyInfoExtractor (main class for combining multiple extractors).
  • Built-in extractors (ReflectionExtractor, PhpDocExtractor, PhpStanExtractor, DoctrineExtractor).

Implementation Patterns

1. Combining Extractors

Use multiple extractors to handle different metadata sources (e.g., PHPDoc, reflection, PHPStan):

$extractors = [
    new ReflectionExtractor(),       // PHP 8 attributes + reflection
    new PhpDocExtractor(),          // DocBlock annotations
    new PhpStanExtractor(),          // PHPStan type hints (if installed)
    new DoctrineExtractor(),        // Doctrine ORM metadata
];

$propertyInfo = new PropertyInfoExtractor($extractors);

2. Dynamic Type Resolution

Resolve types at runtime (e.g., for form validation or API responses):

// Check if a property is writable
$isWritable = $propertyInfo->isWritable(User::class, 'email');

// Get read/write info (e.g., for serialization)
$readInfo = $propertyInfo->getReadInfo(User::class, 'email');
$writeInfo = $propertyInfo->getWriteInfo(User::class, 'email');

3. Integration with Laravel

Validation (e.g., Form Requests)

Use PropertyInfo to dynamically infer validation rules:

use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;

class StoreUserRequest extends FormRequest
{
    public function rules()
    {
        $propertyInfo = app(PropertyInfoExtractorInterface::class);
        $types = $propertyInfo->getTypes(User::class, 'email');

        return [
            'email' => ['required', 'email', ...$types],
        ];
    }
}

API Serialization (e.g., Laravel Sanctum/Fractal)

Generate JSON:API schemas dynamically:

use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;

class UserResource extends JsonResource
{
    public function toArray($request)
    {
        $propertyInfo = app(PropertyInfoExtractorInterface::class);
        $types = $propertyInfo->getTypes(User::class);

        return array_reduce($types, function ($carry, $type) {
            $carry['attributes'][] = [
                'name' => $type['property'],
                'type' => $type['type'] ?? 'string',
            ];
            return $carry;
        }, ['data' => []]);
    }
}

Dynamic Forms (e.g., Laravel Nova/Backpack)

Generate form fields based on property types:

use Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface;

class UserCrudController extends Controller
{
    public function edit(Request $request)
    {
        $propertyInfo = app(PropertyInfoExtractorInterface::class);
        $types = $propertyInfo->getTypes(User::class);

        $fields = collect($types)->mapWithKeys(function ($type) {
            return [
                $type['property'] => match ($type['type']) {
                    'string' => 'text',
                    'integer' => 'number',
                    'boolean' => 'checkbox',
                    default => 'text',
                },
            ];
        });

        return view('user.edit', compact('fields'));
    }
}

4. Custom Extractors

Extend for domain-specific metadata (e.g., custom attributes):

use Symfony\Component\PropertyInfo\Extractor\ExtractorInterface;

class CustomAttributeExtractor implements ExtractorInterface
{
    public function getType(?ReflectionClass $class, ?string $property): ?Type
    {
        if (!$class || !$property) {
            return null;
        }

        $reflectionProperty = $class->getProperty($property);
        $attribute = $reflectionProperty->getAttribute(CustomType::class);

        return $attribute ? new Type($attribute->value) : null;
    }

    // Implement other required methods (getTypes, isReadable, etc.)
}

Register the extractor:

$extractors[] = new CustomAttributeExtractor();

Gotchas and Tips

Pitfalls

  1. Extractor Order Matters

    • Extractors are evaluated in order. Place more reliable sources (e.g., ReflectionExtractor) before fallbacks like PhpDocExtractor.
    • Example: ReflectionExtractor will override PhpDocExtractor if both return types.
  2. PHPStanExtractor Dependency

    • Requires phpstan/phpstan (composer require phpstan/phpstan).
    • Throws exceptions if PHPStan is unavailable (handle gracefully):
      try {
          $propertyInfo->getTypes(User::class, 'email');
      } catch (UnsupportedTypeException $e) {
          // Fallback to other extractors
      }
      
  3. Promoted Properties in PHP 8+

    • PhpDocExtractor may misread promoted properties. Use ReflectionExtractor first:
      $extractors = [new ReflectionExtractor(), new PhpDocExtractor()];
      
  4. DocBlock Parsing Quirks

    • Inline @var tags (e.g., #[param-type var="string"]) may not work. Use full DocBlock syntax:
      /**
       * @var string
       */
      public $email;
      
    • Avoid scalar in DocBlocks (treated as object in older versions).
  5. Performance

    • Caching is not built-in. Cache results manually:
      $cache = new ArrayCache();
      $propertyInfo = new PropertyInfoExtractor($extractors, $cache);
      

Debugging Tips

  • Inspect Extractor Outputs:
    $types = $propertyInfo->getTypes(User::class, 'email');
    dump($types); // Debug raw type data
    
  • Check for Conflicts:
    • If types are inconsistent, reorder extractors or add a custom resolver.
  • Validate with PHPStan: Run phpstan analyse to catch type mismatches early.

Extension Points

  1. Custom Type Resolvers Override type resolution for specific classes:

    $propertyInfo->addTypeResolver(new class implements TypeResolverInterface {
        public function resolve(Type $type, ?ReflectionClass $class, ?string $property): ?Type
        {
            if ($class && $class->getName() === User::class && $property === 'email') {
                return new Type('string|null');
            }
            return null;
        }
    });
    
  2. Event Listeners Extend PropertyInfoExtractor to log or modify metadata:

    $propertyInfo->addListener(new class implements PropertyInfoListenerInterface {
        public function onExtract(ExtractEvent $event)
        {
            if ($event->getProperty() === 'email') {
                $event->setTypes([new Type('string')]);
            }
        }
    });
    
  3. Laravel Service Provider Bind PropertyInfoExtractor in AppServiceProvider:

    public function register()
    {
        $this->app->singleton(PropertyInfoExtractorInterface::class, function () {
            return new PropertyInfoExtractor([
                new ReflectionExtractor(),
                new PhpDocExtractor(),
            ]);
        });
    }
    

Config Quirks

  • No Default Configuration: All extractors are opt-in. Explicitly define which to use.
  • PHP 8.4+: Required for Symfony 8+. Use symfony/property-info:^7.4 for PHP 8.1–8.3.
  • DoctrineExtractor: Needs Doctrine ORM (composer require doctrine/orm).
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.
hamzi/corewatch
minionfactory/raw-hydrator
hexters/coinpayment
rjcodes/rjcms
act-training/laravel-permissions-manager
alimarchal/laravel-chart-of-accounts
babenkoivan/elastic-scout-driver
mkwebdesign/filament-watchdog-v5
renatomarinho/laravel-page-speed
zedmagdy/filament-business-hours
renatovdemoura/blade-elements-ui
devgeek/beacon-admin
benjamin-rqt/data-watcher-bundle
atriumphp/atrium
sandermuller/package-boost-laravel
sandermuller/boost-skills
redaxo/core
yusufgenc/filament-api-forge
l3aro/rating-star-for-filament
leek/filament-subtenant-scope