Code analyzer for extracting symbols, structure, and relationships from PHP files. Includes full support for the Laravel framework. Indexes code for semantic search in Qdrant.
The PHP analyzer parses .php files and extracts:
- Symbols - classes, methods, functions, interfaces, traits, constants
- Relationships - inheritance, implementations, Eloquent relations
- Metadata - PHPDoc, visibility, types, Laravel-specific
- Framework - Eloquent models, Controllers, Routes (Laravel)
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ .php Files │────▶│ PHP Analyzer │────▶│ CodeChunks │
│ (source code) │ │ (VKCOM parser) │ │ (structured) │
└─────────────────┘ └──────────────────┘ └────────┬────────┘
│
┌──────────────────┐ │
│ Laravel Analyzer │◀─────────────┤
│ (Eloquent, etc.) │ │
└──────────────────┘ ▼
┌─────────────────┐
│ Qdrant │
│ (vector store) │
└─────────────────┘
<?php
namespace App\Models;
/**
* Represents a user in the system.
*/
class User extends Model implements Authenticatable
{
use SoftDeletes, Notifiable;
protected $fillable = ['name', 'email'];
protected $casts = ['email_verified_at' => 'datetime'];
}Extracted information:
| Field | Value | Description |
|---|---|---|
name |
"User" |
Class name |
namespace |
"App\\Models" |
Namespace |
full_name |
"App\\Models\\User" |
Fully qualified name |
extends |
"Model" |
Parent class |
implements |
["Authenticatable"] |
Implemented interfaces |
traits |
["SoftDeletes", "Notifiable"] |
Used traits |
is_abstract |
false |
If abstract |
is_final |
false |
If final |
docstring |
"Represents a user..." |
PHPDoc |
/**
* Returns the user's orders.
*
* @param int $limit Maximum number of orders
* @return Collection<Order>
*/
public function getOrders(int $limit = 10): Collection
{
return $this->orders()->limit($limit)->get();
}Extracted information:
| Field | Value | Description |
|---|---|---|
name |
"getOrders" |
Method name |
visibility |
"public" |
Visibility |
is_static |
false |
If static |
is_abstract |
false |
If abstract |
parameters |
[{name: "limit", type: "int", default: "10"}] |
Parameters |
return_type |
"Collection" |
Return type |
phpdoc.params |
[{name: "limit", type: "int", desc: "..."}] |
PHPDoc params |
phpdoc.return |
{type: "Collection<Order>", desc: ""} |
PHPDoc return |
interface PaymentGateway extends Gateway
{
public function charge(float $amount): bool;
public function refund(string $transactionId): bool;
}trait Auditable
{
public function getCreatedBy(): ?User { ... }
public function logActivity(string $action): void { ... }
}/**
* Helper for price formatting.
*/
function format_price(float $amount, string $currency = 'USD'): string
{
return number_format($amount, 2) . ' ' . $currency;
}class Order extends Model
{
protected $fillable = ['user_id', 'total', 'status'];
protected $casts = ['total' => 'decimal:2'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function items(): HasMany
{
return $this->hasMany(OrderItem::class);
}
public function scopeCompleted($query)
{
return $query->where('status', 'completed');
}
public function getTotalFormattedAttribute(): string
{
return number_format($this->total, 2) . ' USD';
}
}Extracted Laravel metadata:
{
"is_eloquent_model": true,
"fillable": ["user_id", "total", "status"],
"casts": {"total": "decimal:2"},
"relations": [
{"name": "user", "type": "belongsTo", "related": "User"},
{"name": "items", "type": "hasMany", "related": "OrderItem"}
],
"scopes": ["completed"],
"accessors": ["total_formatted"]
}class OrderController extends Controller
{
public function index(): View { ... }
public function store(OrderRequest $request): RedirectResponse { ... }
public function show(Order $order): View { ... }
}Controller metadata:
{
"is_controller": true,
"is_resource_controller": true,
"actions": ["index", "store", "show"],
"http_methods": {
"index": "GET",
"store": "POST",
"show": "GET"
}
}Route::get('/orders', [OrderController::class, 'index'])->name('orders.index');
Route::resource('users', UserController::class);php/
├── types.go # PHP types: ClassInfo, MethodInfo, etc.
├── analyzer.go # PathAnalyzer implementation (21KB)
├── api_analyzer.go # APIAnalyzer for documentation
├── phpdoc.go # PHPDoc parser
├── analyzer_test.go # 10 CodeAnalyzer tests
├── api_analyzer_test.go # 4 APIAnalyzer tests
├── parser_test.go # 5 parser tests
├── README.md # This documentation
└── laravel/ # Laravel module
├── types.go # Laravel-specific types
├── analyzer.go # Laravel coordinator
├── adapter.go # PathAnalyzer adapter
├── eloquent.go # Eloquent Models analyzer
├── controller.go # Controllers analyzer
├── routes.go # Routes analyzer
└── README.md # Laravel documentation
import "github.com/doITmagic/rag-code-mcp/internal/ragcode/analyzers/php/laravel"
// For Laravel projects (recommended)
analyzer := laravel.NewAdapter()
// Analyze directories/files
chunks, err := analyzer.AnalyzePaths([]string{"./app"})
for _, chunk := range chunks {
fmt.Printf("[%s] %s\n", chunk.Type, chunk.Name)
if relations, ok := chunk.Metadata["relations"]; ok {
fmt.Printf(" Relations: %v\n", relations)
}
}The PHP/Laravel analyzer is automatically selected for:
php- generic PHP projectslaravel- Laravel projectsphp-laravel- Laravel alternative
| File/Directory | Project Type |
|---|---|
artisan |
Laravel |
composer.json |
PHP |
app/Models/ |
Laravel |
routes/web.php |
Laravel |
| Type | Description | Example |
|---|---|---|
class |
PHP class | class User extends Model |
method |
Class method | public function save() |
function |
Global function | function helper() |
interface |
Interface | interface Payable |
trait |
Trait | trait Auditable |
const |
Class constant | const STATUS_ACTIVE = 1 |
property |
Property | protected $fillable |
{
"namespace": "App\\Models",
"extends": "Model",
"implements": ["Authenticatable"],
"traits": ["SoftDeletes"],
"is_abstract": false,
"is_final": false,
"is_eloquent_model": true,
"fillable": ["name", "email"],
"relations": [...]
}{
"class_name": "UserController",
"visibility": "public",
"is_static": false,
"is_abstract": false,
"is_final": false,
"parameters": [...],
"return_type": "View",
"phpdoc": {
"description": "...",
"params": [...],
"return": {...}
}
}# All PHP tests (19 tests)
go test ./internal/ragcode/analyzers/php/...
# Laravel only (21 tests)
go test ./internal/ragcode/analyzers/php/laravel/...
# With coverage
go test -cover ./internal/ragcode/analyzers/php/...Results:
- ✅ 19/19 PHP tests PASS
- ✅ 21/21 Laravel tests PASS
- ✅ Coverage: 83.6%
- VKCOM/php-parser v0.8.2 - PHP parser with PHP 8.0-8.2 support
| Limitation | Description |
|---|---|
| No Runtime | Static analysis, doesn't execute code |
| Single-file | Each file is analyzed independently |
| No Autoload | Doesn't resolve Composer autoload |
- Route groups with middleware
- Migration analyzer
- Symfony framework support
- WordPress support
- Cross-file type resolution
-
Eloquent Models (COMPLETE):
- ✅ Model detection (
extends Model) - ✅ Property extraction:
$fillable,$guarded,$casts,$table,$primaryKey - ✅ All Relations:
hasOne,hasMany,belongsTo,belongsToMany,hasManyThrough,morphTo,morphMany,morphToMany,morphedByMany - ✅ Foreign key & local key extraction
- ✅ Fully-qualified name resolution with imports
- ✅ Scopes:
scopeMethodName()detection - ✅ Accessors/Mutators:
getXxxAttribute(),setXxxAttribute() - ✅ SoftDeletes trait detection
- ✅ Model detection (
-
Controllers (COMPLETE):
- ✅ Controller detection (
extends Controller) - ✅ Resource controller identification (7 RESTful actions)
- ✅ API controller detection
- ✅ HTTP method inference from action names
- ✅ Parameter extraction
- ✅ Controller detection (
-
Routes (COMPLETE):
- ✅ Route file parsing (
routes/web.php,routes/api.php) - ✅
Route::get(),Route::post(), etc. - ✅
Route::match()support - ✅
Route::resource()expansion - ✅ Controller@action binding
- ✅ Array syntax
[Controller::class, 'action']
- ✅ Route file parsing (
-
Namespace Support
- Multi-namespace per-file
- Fully qualified names
-
Class Analysis
- Class declarations with extends/implements
- Method extraction (visibility, static, abstract, final)
- Property extraction (visibility, static, readonly, typed)
- Class constants (visibility, value extraction)
- Parameter and return type support
- PHPDoc extraction (description, @param, @return)
-
Interface Support
- Interface declarations
- Method signatures
- Multiple interface extends
- PHPDoc documentation
-
Trait Support
- Trait declarations
- Methods and properties
- PHPDoc documentation
-
Function Analysis
- Global functions
- Namespaced functions
- Parameters and return types
- PHPDoc documentation
-
PHPDoc Parsing
- Description extraction
- @param tags (type, name, description)
- @return tags (type, description)
- @throws, @var, @deprecated, @see, @example tags
- Type hint merging with PHPDoc
- Total Lines: ~1,800
- Core Implementation:
analyzer.go(21KB, 814 lines)api_analyzer.go(7.4KB, 293 lines)phpdoc.go(5.3KB, 217 lines)
- Helper Functions: 25+
- Test Coverage: 83.6%
- Tests: 19 (5 parser + 10 analyzer + 4 API)
- Integration Tests: 6 (language manager + workspace detector)
Follows the same pattern as golang analyzer with modular framework support:
php/
├── types.go - Internal type definitions
├── analyzer.go - PathAnalyzer implementation
├── api_analyzer.go - APIAnalyzer implementation
├── phpdoc.go - PHPDoc parser (PHP-specific helper)
├── analyzer_test.go - CodeAnalyzer tests
├── api_analyzer_test.go - APIAnalyzer tests
├── parser_test.go - Parser validation tests
└── laravel/ - Laravel framework module (separate package)
├── types.go - Laravel-specific types (Eloquent, Controllers, Routes)
├── analyzer.go - Laravel framework analyzer coordinator
├── eloquent.go - Eloquent Model analyzer
├── controller.go - Controller analyzer
└── README.md - Laravel module documentation
Framework-specific analyzers are separated into their own packages:
- laravel/ - Laravel framework support (Eloquent, Controllers, Routes)
- symfony/ - Symfony framework support (planned)
- wordpress/ - WordPress support (planned)
This modular design allows:
- Clean separation of concerns
- Independent testing of framework features
- Easy addition of new frameworks
- Reusable base PHP analyzer