PHP Analyzer
August 6, 2026 · View on GitHub
Introduction
The PHP Analyzer module is a language-specific static analysis component within the Language_Analyzers family. It is responsible for parsing PHP source files, extracting structural code entities (classes, interfaces, traits, enums, functions, and methods), and identifying dependency relationships between them (inheritance, interface implementation, object instantiation, static calls, and constructor property promotion).
The module is invoked by the Dependency_Analysis_Service's CallGraphAnalyzer whenever a .php (or .phtml/.inc) file is encountered during repository analysis, and its output feeds directly into the Dependency_Analyzer_Core data models (Node, CallRelationship) that power the rest of the CodeWiki documentation-generation pipeline.
It consists of two core components defined in codewiki/src/be/dependency_analyzer/analyzers/php.py:
| Component | Responsibility |
|---|---|
NamespaceResolver | Tracks PHP namespace declarations and use statements to resolve short/aliased class names to fully-qualified names (FQNs). |
TreeSitterPHPAnalyzer | Drives a tree-sitter-php parse of a single file, walking the AST to build Node and CallRelationship objects. |
A module-level convenience function, analyze_php_file(file_path, content, repo_path), wraps the analyzer class and is the single entry point used by the rest of the system.
Architecture Overview
The PHP Analyzer follows the same "parse → extract nodes → extract relationships" pattern used by its sibling language analyzers (see C-Family_Tree-sitter_Analyzers, JavaScript_TypeScript_Analyzers, and Python_Analyzer), but is tailored to PHP-specific syntax such as namespaces, traits, enums, and constructor property promotion.
graph TD
subgraph "PHP_Analyzer module"
A[analyze_php_file] --> B[TreeSitterPHPAnalyzer]
B --> C[NamespaceResolver]
B --> D["tree-sitter-php Parser"]
B --> E["Node list"]
B --> F["CallRelationship list"]
end
G["Dependency_Analysis_Service.CallGraphAnalyzer"] -->|"_analyze_php_file()"| A
E --> H["Node model (Dependency_Analyzer_Core)"]
F --> I["CallRelationship model (Dependency_Analyzer_Core)"]
style A fill:#dff,stroke:#333
style B fill:#dff,stroke:#333
style C fill:#dff,stroke:#333
Component Relationships
classDiagram
class NamespaceResolver {
-str current_namespace
-Dict~str,str~ use_map
+register_namespace(ns)
+register_use(fqn, alias)
+resolve(name) str
}
class TreeSitterPHPAnalyzer {
-Path file_path
-str content
-str repo_path
-List~Node~ nodes
-List~CallRelationship~ call_relationships
-NamespaceResolver namespace_resolver
-Dict~str,Node~ _top_level_nodes
+__init__(file_path, content, repo_path)
-_analyze()
-_extract_namespace_info(node, depth)
-_extract_use_statement(node)
-_extract_nodes(node, lines, depth, parent_class)
-_extract_relationships(node, depth)
-_add_use_relationships(node)
-_is_template_file() bool
-_is_primitive(type_name) bool
}
class Node {
<<Pydantic model - Dependency_Analyzer_Core>>
}
class CallRelationship {
<<Pydantic model - Dependency_Analyzer_Core>>
}
TreeSitterPHPAnalyzer --> NamespaceResolver : uses
TreeSitterPHPAnalyzer --> Node : produces
TreeSitterPHPAnalyzer --> CallRelationship : produces
Core Components
NamespaceResolver
PHP allows classes to be referenced by short names, aliases, or fully-qualified names depending on the current namespace and any use import statements in scope. NamespaceResolver centralizes this logic so the analyzer can always emit a consistent, fully-qualified dependency target.
Responsibilities:
register_namespace(ns)— records the file's current namespace (from anamespace_definitionAST node).register_use(fqn, alias=None)— records an imported class, keyed by its alias (or its own short name if no alias is given). Supports both simple (use App\User;) and grouped (use App\{User, Post};) use-statement forms.resolve(name)— given any class-name reference encountered in the code, returns its fully-qualified form by:- Returning as-is if already fully qualified (leading
\). - Looking up the exact name in the alias map.
- Looking up the first namespace segment in the alias map (for partially-qualified names).
- Falling back to prefixing with the current namespace.
- Returning the name unchanged if no namespace context exists.
- Returning as-is if already fully qualified (leading
flowchart LR
Start([name to resolve]) --> Q1{Starts with backslash?}
Q1 -- yes --> R1[Strip leading backslash, return]
Q1 -- no --> Q2{Full name in use_map?}
Q2 -- yes --> R2[Return mapped FQN]
Q2 -- no --> Q3{First segment in use_map?}
Q3 -- yes --> R3["Return mapped_base + remaining segments"]
Q3 -- no --> Q4{current_namespace set?}
Q4 -- yes --> R4["Return current_namespace + '\\' + name"]
Q4 -- no --> R5[Return name unchanged]
TreeSitterPHPAnalyzer
The main analyzer class. It wraps tree_sitter_php.language_php() (the mixed PHP/HTML grammar, chosen because it is the most common real-world case) and performs a three-pass analysis of the file's AST.
Constructor behavior (__init__)
- Stores
file_path,content, andrepo_path. - Initializes empty
nodes,call_relationships, a freshNamespaceResolver, and a_top_level_nodeslookup dict. - Checks
_is_template_file()— if the file matches known template patterns/directories (.blade.php,.phtml,.twig.php, or paths containingviews/templates/resources/views), analysis is skipped entirely (templates are typically embedded HTML+PHP with little structural code value). - Otherwise calls
_analyze().
_analyze() — three-pass pipeline
sequenceDiagram
participant Caller as analyze_php_file()
participant TA as TreeSitterPHPAnalyzer
participant TS as tree-sitter-php Parser
participant NR as NamespaceResolver
Caller->>TA: __init__(file_path, content, repo_path)
TA->>TA: _is_template_file()?
alt is template
TA-->>Caller: nodes=[], relationships=[]
else not template
TA->>TS: parser.parse(content)
TS-->>TA: AST root
TA->>TA: Pass 1: _extract_namespace_info(root)
TA->>NR: register_namespace() / register_use()
TA->>TA: Pass 2: _extract_nodes(root, lines)
Note over TA: builds Node objects for classes,<br/>interfaces, traits, enums, functions, methods
TA->>TA: Pass 3: _extract_relationships(root)
TA->>NR: resolve(name) for each type reference
Note over TA: builds CallRelationship objects for<br/>extends, implements, new, static calls,<br/>constructor promotion, use imports
TA-->>Caller: nodes, call_relationships
end
-
Namespace/Use extraction (
_extract_namespace_info) — recursively walks the tree looking fornamespace_definitionandnamespace_use_declarationnodes, populating theNamespaceResolverbefore any name resolution is needed. -
Node extraction (
_extract_nodes) — recursively walks the tree recognizing these AST node types and mapping them to acomponent_type:AST node type component_type Notes class_declarationclassorabstract classDetects abstractmodifierinterface_declarationinterfacetrait_declarationtraitenum_declarationenumfunction_definitionfunctionTop-level function method_declarationmethodName qualified as ClassName.methodNameFor each recognized node it builds a
Node(from Dependency_Analyzer_Core) populated with:id/component_id(computed via_get_component_id, formatrelative/path.php::Nameorrelative/path.php::Class.method),source_codeslice,start_line/end_line, PHPDocdocstring(via_get_preceding_docstring),parameters(via_extract_parameters), andbase_classes(via_extract_base_classes). The current enclosing class name is threaded through recursion via theparent_classargument so that methods are correctly attributed and named. -
Relationship extraction (
_extract_relationships) — a second recursive walk that emitsCallRelationshipobjects for the following dependency kinds:Trigger AST node Relationship meaning Notes namespace_use_declarationfile → imported class via _add_use_relationships, caller is the file's relative pathclass_declarationwithbase_clauseclass → parent class ( extends)class_declaration/enum_declarationwithclass_interface_clauseclass/enum → interface ( implements)object_creation_expressioncontaining class → instantiated type ( new)scoped_call_expressioncontaining class → target class ( ::static call)property_promotion_parametercontaining class → promoted property's type (PHP 8+ constructor promotion) Every extracted type name is passed through
NamespaceResolver.resolve()and filtered via_is_primitive()(which excludes PHP scalar types and common built-ins such asException,Closure,DateTime, etc., listed inPHP_PRIMITIVES). Resolved names have their namespace separator (\) converted to.to match the dotted component-ID convention used elsewhere in the system. All relationships are emitted withis_resolved=False; final resolution to actual component IDs happens later inCallGraphAnalyzer._resolve_call_relationships(see Dependency_Analysis_Service).
Supporting helpers
_get_module_path/_get_relative_path/_get_component_id— compute file-relative paths and component identifiers, stripping.php/.phtml/.incextensions._find_child_by_type,_get_name_from_node,_get_type_from_clause,_extract_type_name,_find_containing_class_name— low-level tree-sitter node traversal utilities._get_preceding_docstring— scans backward from a node for a/** ... */PHPDoc block, either viaprev_named_siblingor raw line scanning._extract_parameters— pulls parameter names (and types, if annotated) fromformal_parameters, supporting simple, promoted, and variadic parameters._extract_base_classes— collects bothextendsandimplementstargets into a single list for theNode.base_classesfield.MAX_RECURSION_DEPTH(100) guards all recursive walks against pathological/deeply nested ASTs, raising a caughtRecursionErrorif exceeded.
analyze_php_file(file_path, content, repo_path=None)
The public functional entry point. Instantiates TreeSitterPHPAnalyzer and returns (analyzer.nodes, analyzer.call_relationships). This is the exact signature expected by CallGraphAnalyzer._analyze_php_file in the Dependency_Analysis_Service.
Integration with the Wider System
graph LR
subgraph "Repository Analysis Pipeline"
RA["RepoAnalyzer<br/>(file discovery)"] --> CGA["CallGraphAnalyzer<br/>(Dependency_Analysis_Service)"]
CGA -->|".php files"| PHP["PHP_Analyzer<br/>analyze_php_file()"]
CGA -->|".py files"| PY["Python_Analyzer"]
CGA -->|".ts/.js files"| TS["JavaScript_TypeScript_Analyzers"]
CGA -->|".c/.cpp/.java/.cs/.kt files"| CF["C-Family_Tree-sitter_Analyzers"]
PHP --> NODES["Node & CallRelationship objects"]
PY --> NODES
TS --> NODES
CF --> NODES
NODES --> RESOLVE["_resolve_call_relationships()<br/>cross-language callee resolution"]
RESOLVE --> DPB["DependencyGraphBuilder<br/>(Dependency_Analyzer_Core)"]
DPB --> DOC["DocumentationGenerator<br/>(Backend_LLM_&_Documentation_Services)"]
end
- Discovery:
RepoAnalyzer(in Dependency_Analysis_Service) walks the repository and classifies.php/.phtml/.incfiles by extension viaCODE_EXTENSIONS. - Dispatch:
CallGraphAnalyzer.analyze_code_filescalls_analyze_code_file, which routes PHP files to_analyze_php_file, which in turn callsanalyze_php_filefrom this module. - Aggregation: The returned
Nodeobjects are merged intoCallGraphAnalyzer.functions(keyed by component ID) andCallRelationshipobjects appended toCallGraphAnalyzer.call_relationships. - Cross-file/cross-language resolution:
CallGraphAnalyzer._resolve_call_relationshipsattempts to resolve each unresolvedCallRelationship.callee(still expressed as a dotted/short type name from this analyzer) against the global and per-language indexes of all discovered components, marking matchesis_resolved=Trueand rewritingcalleeto the actual component ID. Unresolved relationships pointing to external/library symbols are filtered out. - Graph construction:
DependencyParser(in Dependency_Analyzer_Core) consumes the final function/relationship lists to build theNode.depends_onsets used throughout the rest of the documentation pipeline (module grouping, dependency graphs, LLM context assembly). - Documentation generation: Ultimately this structural/dependency data is consumed by Backend_LLM_&_Documentation_Services to generate human-readable module documentation — the same kind of document you are reading now.
Data Model Reference
The PHP Analyzer produces instances of two Pydantic models owned by Dependency_Analyzer_Core:
Node— represents a single extracted PHP entity (class, interface, trait, enum, function, or method) with fields such asid,name,component_type,file_path,relative_path,source_code,start_line/end_line,docstring,parameters,base_classes,class_name, anddisplay_name.CallRelationship— represents a directed dependency edge (caller→callee) with an optionalcall_lineand anis_resolvedflag indicating whethercalleehas been matched to a concrete component ID.
Refer to Dependency_Analyzer_Core for the full model definitions and how they are consumed downstream (dependency graph building, analysis result serialization).
Key Design Notes
- Template file skipping: Files matching Blade/Twig/
.phtmlpatterns or living inviews/templates/resources/viewsdirectories are skipped entirely (_is_template_file), since these typically mix large amounts of HTML with minimal reusable PHP logic and would otherwise pollute the dependency graph. - Mixed PHP/HTML grammar: The analyzer deliberately uses
tree_sitter_php.language_php()rather than the pure-PHP grammar, since most real-world PHP files (including non-template ones) may contain embedded HTML. - Primitive/built-in filtering:
PHP_PRIMITIVESprevents noise from scalar types (string,int, etc.) and ubiquitous SPL/core classes (Exception,Closure,DateTime, etc.) from appearing as false dependency edges. - Namespace-aware resolution: Unlike simpler language analyzers, PHP's namespace/use-import system requires the dedicated
NamespaceResolverto correctly disambiguate short class names — this is unique among the analyzers in Language_Analyzers. - Deferred resolution: Consistent with all other analyzers, this module does not resolve relationships to final component IDs itself — it only produces best-effort namespace-qualified names; the heavy lifting of matching across the whole repository is delegated to
CallGraphAnalyzerin Dependency_Analysis_Service.