14 KiB
Encapsulation Review
Summary
| Metric | Value |
|---|---|
| Entity classes with all-public fields | 9/9 (100%) |
| FunctionContext public properties | 26 (all mutable) |
| CompilerBase private methods | 5/269 (1.9%) |
Traits with direct $this->context-> access |
6 traits, 60+ sites |
| ScopeContext (dead code) | 7 lines, empty class |
Issue 1: Entity classes — all-public mutable fields
Severity: High. Every entity class exposes all internal state as public writable properties. External code in Preprocessor/CompilerBase directly mutates them.
1.1 ClassDef (18 public properties)
src/Php/Entity/ClassDef.php
public array $methods = []; // externally populated: $classDef->properties[$name] = ...
public array $properties = []; // externally populated
public array $constants = []; // externally populated
public array $implements = []; // externally populated
public string $extends = ''; // externally set: $this->classDef->extends = ...
public bool $requireCtor = false;
public bool $enum = false;
public ?string $enumBackingType = null;
public array $enumCases = [];
public array $abstractMethods = [];
public ?Trait_ $trait = null;
public array $traitAliases = [];
public array $traitIgnored = [];
public int $flags; // no visibility checks, raw bitmask
public bool $inheritedFromInternalClass = false;
public string $ctorInit = ''; // mutated during code generation
public string $ctorClean = ''; // mutated during code generation
public FunctionContext $propertyContext; // set after construction
Issues:
$properties,$methods,$constants— exposed as raw arrays. External code does$classDef->properties[$name] = $propDef. No validation that the key matches$propDef->name, no type enforcement.$flags— raw int, no guarantee it's a valid Modifiers bitmask.$ctorInit/$ctorClean— mutated by CompilerBase during code generation, not initialization.- Property additions use
addMethod(),addAbstractMethod()but array properties are also set directly via[] =. $extends— set directly as raw string, bypassesparent::__construct()which also sets it on ClassLikeDef.
Recommendation:
- Make
$methods,$properties,$constantsprivate, expose viaaddMethod()/getMethod()(already exists) - Make
$flagsprivate, exposeisAbstract()(already exists), addisFinal(),isReadonly() - Make
$extendswrite-once viasetExtends(string)with validation - Add
appendCtorInit(string)andappendCtorClean(string)methods instead of direct string mutation
1.2 FunctionDef (12 public properties)
src/Php/Entity/FunctionDef.php
public string $name;
public string $returnType;
public array $argInfoList = []; // externally populated: $functionDef->argInfoList[] = $argInfo
public int $argCountRequired = 0;
public string $params = ''; // generated C++ param string, mutated during compilation
public string $namespace;
public bool $method = false;
public bool $stub = false;
public bool $returnTypeUndeclared = false;
public string $returnClass = '';
public ?array $returnTypeCheck = null;
public string $returnTypeStr = '';
public ?NodeAbstract $returnTypeNode = null;
Issues:
$nameand$namespaceare set in constructor but still publicly writable — should be readonly$argInfoList[]is directly appended to by Preprocessor (line 329)$paramsis a codegen artifact stored on the entity — belongs in a separate compilation context
Recommendation:
- Make constructor-set properties readonly (
$name,$namespace,$returnType) - Add
addArg(ArgInfo $arg)method instead of direct array mutation - Extract
$paramsto a compilation context separate from the definition entity
1.3 PropertyDef (7 public properties)
src/Php/Entity/PropertyDef.php
public string $name;
public string $type;
public int $flags;
public ?string $default = null;
public ?ArrayInitPlan $arrayInitPlan = null;
public bool $nullable = false;
public string $class = ''; // set after construction
Issues:
$classis set after construction externally ($propDef->class = $fullClassName)$flagsis raw int — already hasisPrivate()/isProtected()/isPublic()/isStatic()methods, good- Constructor already sets all core fields —
$classshould be added to the constructor
Recommendation:
- Add
$classto the constructor (it's always known at construction time) - Make constructor-set fields readonly or private
1.4 MethodDef (4 public properties)
src/Php/Entity/MethodDef.php
public int $flags;
public string $name;
public ?FunctionDef $functionDef = null; // set after construction
public bool $hasDynamicCall = false;
Issues:
- No flag-check methods — inline checks in CompilerBase should use
$methodDef->isPrivate()instead $functionDefis set externally:$this->methodDef->functionDef = $functionDef(Preprocessor line 411)
Recommendation:
- Add
HasFlagstrait (from Phase 3.1 of reuse plan) - Add
setFunctionDef(FunctionDef $fd)method with validation
1.5 ConstantDef (8 public properties)
public string $name;
public string $type;
public int $flags;
public string $value;
public string $arrayExpr = '';
public string $class = '';
public ?NodeAbstract $valueExpr = null;
Issues: Same pattern — constructor sets core fields, but $class is set externally afterward.
Recommendation: Add $class to constructor.
Issue 2: FunctionContext — public mutable grab-bag
Severity: High. 26 public properties, all writable by any code with access to the context object.
src/Php/Context/FunctionContext.php
public ?SsaBuilder $ssaBuilder = null; // transient analysis state
public array $stableObjects = []; // SSA optimizer state
public array $hoistedProps = []; // SSA optimizer state
public array $unsafeObjectProps = []; // SSA optimizer state
public array $objects = []; // object variable tracking
public array $stdArrays = []; // std container tracking
public array $stdContainers = []; // std container tracking
public array $localVars = []; // local variable table
public array $staticVars = []; // static variable table
public array $globalVars = []; // global variable table
public array $ceWrappers = []; // class entry wrappers
public int $tmpVarIndex = 0; // auto-increment counter
public array $arguments = []; // function arguments
public bool $inLoop = false; // control-flow state
public bool $inClosure = false; // control-flow state
public bool $hasMultiLevelBreak = false;
public bool $hasMultiLevelContinue = false;
public bool $inAssignExpr = false; // expression context
public array $beforeStmtLines = []; // deferred code (flushed before stmts)
public array $afterStmtLines = []; // deferred code (flushed after stmts)
public array $objectProps; // (uninitialized!)
public array $staticPropRefs = []; // static property references
public int $scopeLevel = 0; // lexical scope depth
/** @var array<int, ScopeContext> */
public array $scopeLayouts = []; // per-scope data
Issues:
- Traits directly mutate deeply nested state:
$this->context->stdArrays[$var] = ...,$this->context->localVars[$name] = ... - No semantic grouping — analysis state, variable tracking, control-flow flags all mixed
$objectPropsis declared but never initialized (could be null at runtime)$scopeLayoutsis managed throughenterScope()/leaveScope()— but can be bypassed$tmpVarIndexauto-increment — should use a method instead of direct++
Recommendation:
- Group related properties into sub-objects:
VariableTable,ControlFlowState,ScopeManager - Make properties that should only be read by the compiler layer private/protected with getters
- Add
incrementTmpVar(): int,addLocalVar(),addBeforeStmt()methods - Initialize
$objectProps = []
Issue 3: CompilerBase — only 1.9% private methods
Severity: Medium. Virtually everything is public or protected.
src/Php/CompilerBase.php — 269 methods total:
- ~25 public methods (many should be protected or internal)
- ~239 protected methods (most should be private — internal helpers)
- 5 private methods (1.9%)
3.1 Methods that should be private
The following methods are internal helpers only called from within CompilerBase (not from Preprocessor, Translator, or traits). They are unnecessarily protected:
| Method | Line | Called from |
|---|---|---|
resetFunction() |
833 | Internal only |
resetMethod() |
840 | Internal only |
resetClass() |
846 | Internal only |
resolveObjectClassDef() |
816 | Already private ✓ |
getBigIntLiteralString() |
1183 | Already private ✓ |
getDecimalLiteralString() |
1188 | Already private ✓ |
parseBeforeStmtLines() |
1321 | Internal, but accessed by traits |
parseAfterStmtLines() |
1331 | Internal, but accessed by traits |
genTmpVarName() |
734 | Public — should at least be protected |
3.2 Public methods that are internal concern
| Method | Current visibility | Issue |
|---|---|---|
genTmpVarName() |
public | Only used internally for variable name generation |
writeFile() |
public | File I/O — should be a separate service |
stop() |
public | Error helper — could be internal |
isScalarInt() |
public | AST helper, only used internally |
getType() |
public | AST helper, only used internally |
getObjectType() |
public | Type mapping, used internally |
getTypeFromZendType() |
public | Type mapping, used internally |
getIncludeDir() |
public | Config getter — should be on a Config object |
getBuildDir() |
public | Config getter — should be on a Config object |
3.3 Public constants leaked as API
30 public constants for internal type names, literal values, etc. These are needed by traits but expose internal naming conventions.
Issue 4: Trait → Context coupling
Severity: Medium. Traits bypass any encapsulation boundary and directly mutate $this->context.
| Trait | $this->context-> accesses |
|---|---|
StdContainerTrait |
40+ accesses to stdArrays, stdContainers, objects, localVars |
LoopVarOptimizer |
accesses to localVars, arguments, scopeLevel, inLoop |
SsaPropOptimizer |
accesses to stableObjects, hoistedProps, unsafeObjectProps, objects |
FuncCallOptimizer |
accesses to beforeStmtLines, arguments, localVars |
SsaTypeOptimizer |
accesses to localVars |
BinaryOpTrait |
accesses to objects, localVars |
Issues:
- Traits have no declared contract — they assume
$this->contextexists and has specific properties - If a property name changes in FunctionContext, all 6 traits break silently
- No type safety — arrays are indexed by string but accessed with arbitrary keys
Recommendation:
- Define a
ContextAccessinterface that traits must use instead of direct property access - Or: inject context into trait methods as a parameter instead of reading from
$this - Short-term: add
@property-readannotations to document the contract
Issue 5: Preprocessor directly mutates entity state
Severity: Medium. Preprocessor bypasses entity boundaries.
src/Php/Preprocessor.php:
line 279: $argInfo->name = $name; // direct property set
line 329: $functionDef->argInfoList[] = $argInfo; // direct array append
line 411: $this->methodDef->functionDef = $functionDef; // direct property set
line 447: $this->classDef->extends = $this->parentClass; // direct property set
line 585: $this->classDef->constants[$constInfo->name] = ...; // direct array set
line 618: $this->classDef->properties[$name] = $propDef; // direct array set
Recommendation: Use entity methods: $functionDef->addArg($argInfo), $this->methodDef->setFunctionDef($functionDef), $classDef->addProperty($propDef), etc.
Issue 6: CompilerBase protected state leaked to inheritance chain
Severity: Low-Medium. The chain CompilerBase → Preprocessor → Translator means any protected property in CompilerBase is accessible from Translator.
CompilerBase has ~50 protected properties. Translator is 3301 lines and accesses many of them. There's no way to know which properties are "safe to use" vs "internal to CompilerBase."
Recommendation: Migrate internal-only properties to private over time, with explicit getter methods where needed.
Issue 7: Platform/Backend — well encapsulated
Severity: None. The Platform and Backend layers are well-encapsulated:
- All state is private (e.g.,
$compilerCommand,$linkerCommandin GccLikeBackend) - Only methods are public
- Abstract contracts are clear
- Factory pattern is used consistently
No changes needed in this layer.
Issue 8: ScopeContext is dead code
Severity: Low. src/Php/Context/ScopeContext.php — 7 lines, empty class body. Used as a placeholder type in FunctionContext's $scopeLayouts array. Either populate it or remove it.
Implementation Priority
| # | Issue | Impact | Effort | Risk |
|---|---|---|---|---|
| 1.1 | Entity: readonly for constructor fields | Data integrity | 2h | Low |
| 1.2 | Entity: add mutation methods (addArg, addProperty, etc.) | Safe mutation | 3h | Medium |
| 2.1 | FunctionContext: group properties into sub-objects | Clarity | 4h | Medium |
| 2.2 | FunctionContext: add accessor methods | Controlled mutation | 3h | Medium |
| 3 | CompilerBase: demote public→protected, protected→private | Boundary clarity | 4h | Medium |
| 4 | Define trait context contract | Safe coupling | 3h | Medium |
| 5 | Preprocessor: use entity methods | Consistent mutation | 2h | Low |
| 8 | Remove ScopeContext dead code | Cleanup | 15m | None |
Recommended order: Start with 8 (quick win), then 1.1 + 1.2 (entity cleanup), then 2.1 + 2.2 (context cleanup), then 3 + 5 + 4 (CompilerBase boundary).