laravel/surveyor
Laravel Surveyor is a mostly static analysis tool for PHP/Laravel that scans files or classes to extract rich metadata (classes, methods, properties, types, bindings, models) in a structured format for other tools. Beta API; may touch DB for model inspection.
Installation:
composer require laravel/surveyor
No additional configuration is required for basic usage.
First Analysis:
use Laravel\Surveyor\Analyzer\Analyzer;
$analyzer = app(Analyzer::class);
$result = $analyzer->analyzeClass(\App\Models\User::class);
$classResult = $result->result();
This immediately returns structured metadata about the User class.
Key First Use Cases:
Analyzer Class: Core entry point for analysis.ClassLikeResult: Primary object returned for class inspection.Laravel\Surveyor\Types\Type for creating and comparing types programmatically.isModelRelation() for Eloquent models.$analyzer = app(Analyzer::class);
$result = $analyzer->analyzeClass(\App\Models\User::class);
// Extract database columns
$columns = collect($result->result()->properties())
->filter(fn($prop) => $prop->isDatabaseAttribute())
->pluck('name');
// Extract relationships
$relationships = $result->result()->methods()
->filter(fn($method) => $method->isModelRelation())
->pluck('name');
$method = $result->result()->getMethod('store');
$returnType = $method->returnType();
if ($returnType->isSame(Type::class(\App\Models\User::class))) {
// Handle User return type
} elseif ($returnType->isSame(Type::arrayShape(
Type::string(),
Type::class(\App\Models\User::class)
))) {
// Handle array of Users
}
// Enable disk caching (e.g., in a service provider)
AnalyzedCache::enableDiskCache(storage_path('surveyor-cache'));
// Subsequent analyses will use cached results
$result = $analyzer->analyzeClass(\App\Models\User::class);
// Analyze a service bound in the container
$serviceClass = app()->getBinding(\App\Services\PaymentService::class);
$result = $analyzer->analyzeClass($serviceClass);
Route::get('/model-meta', function () {
$analyzer = app(Analyzer::class);
$meta = $analyzer->analyzeClass(\App\Models\Post::class)->result();
return response()->json($meta->toArray());
});
Extend Analyzer to add domain-specific logic:
class CustomAnalyzer extends Analyzer {
public function analyzeDomainSpecific(ClassLikeResult $result) {
if ($result->name() === \App\Models\User::class) {
return $this->extractUserSpecificMetadata($result);
}
}
}
$type = Type::from($method->returnType()->toString());
if ($type->isSame(Type::union(
Type::class(\App\Models\User::class),
Type::null()
))) {
// Handle nullable User
}
$scope = $analyzer->analyze(__FILE__)->analyzed();
$state = $scope->state();
// Inspect variable types in method bodies
$variables = $state->variables();
foreach ($variables as $var) {
if ($var->type()->isSame(Type::class(\Illuminate\Http\Request::class))) {
// Handle Request variable
}
}
Non-Static Analysis Quirks:
Caching Issues:
AnalyzedCache::clear();
Type Inference Limitations:
@property docblocks may return MixedType.array or string may cause resolution issues (fixed in v0.2.4).Performance:
AnalyzedCache::enableDiskCache(storage_path('surveyor-cache'));
Inspect Raw Analysis:
$result = $analyzer->analyzeClass(\App\Models\User::class);
dump($result->analyzed()->toArray()); // Debug scope
dump($result->result()->toArray()); // Debug class result
Handle Missing Classes:
try {
$result = $analyzer->analyzeClass(\NonExistentClass::class);
} catch (\Laravel\Surveyor\Exceptions\ClassNotFoundException $e) {
// Fallback logic
}
Validate Types:
$type = Type::from($method->returnType()->toString());
if (!$type->isValid()) {
// Handle invalid type (e.g., unsupported union)
}
Clear Cache on Changes:
php artisan surveyor:clear-cache
(Note: As of v0.2.6, this is not a built-in command; use AnalyzedCache::clear() programmatically.)
Custom Type Resolvers:
Extend the type system by creating a custom Type resolver:
class CustomTypeResolver implements \Laravel\Surveyor\Types\Contracts\TypeResolver {
public function resolve(string $typeString): ?Type {
if (str_starts_with($typeString, 'App\\Custom\\')) {
return Type::class($typeString);
}
return null;
}
}
Register it via the Analyzer service provider.
Hook into Analysis Pipeline: Use events or decorators to modify analysis results:
$analyzer->analyzeClass(\App\Models\User::class)
->then(function ($result) {
// Post-process result
});
Override Model Analysis:
For Eloquent models, extend ModelAnalyzer to add custom logic:
class CustomModelAnalyzer extends \Laravel\Surveyor\Analyzer\ModelAnalyzer {
protected function analyzeModelRelationships(ClassLikeResult $result) {
// Custom relationship detection
}
}
Add Custom DocBlock Parsing:
Extend DocBlockParser to support custom annotations:
class CustomDocBlockParser extends \Laravel\Surveyor\Parser\DocBlockParser {
protected function parseCustomAnnotations(string $content) {
// Handle @custom annotations
}
}
Environment Variables:
SURVEYOR_CACHE_ENABLED: Defaults to false; enable for production.SURVEYOR_CACHE_DIR: Must be writable by the PHP process.Facade Resolution:
Surveyor resolves facades to their root classes (e.g., Auth::user() → \Illuminate\Auth\AuthManager). If this causes issues, disable facade resolution in the Analyzer config.
Generics Support:
User::where(...)->get()) now propagate generics correctly (fixed in v0.2.4).Inertia-Specific Types:
Surveyor supports Inertia’s special props (e.g., @prop('user' => ['type' => 'Inertia\InertiaResponse'])). If missing, ensure your Inertia versions are compatible.
How can I help you explore Laravel packages today?