Inappropriate Intimacy
debt(d5/e5/b5/t5)
Closest to 'specialist tool catches it' (d5). PHPMD and PHPMetrics (listed in detection_hints.tools) can flag coupling metrics and some patterns of inappropriate intimacy, but they catch it heuristically — high afferent/efferent coupling counts, not the nuanced semantic issue. Many forms (e.g., Reflection abuse, bidirectional dependencies, deep getter chains) require careful code review, pushing toward d7, but the tooling availability pulls it back to d5.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix says to move methods/data between classes or introduce dedicated methods — this typically touches at least two classes and all their callers. It's not a one-line fix (ruling out e1-e3), but it's usually contained within a component boundary rather than being a cross-cutting architectural rework, so e5 fits well.
Closest to 'persistent productivity tax' (b5). Inappropriate intimacy applies across all contexts (web, cli, queue-worker) and is an OOP coupling smell that, once established, slows down multiple work streams — any change to either intimately coupled class risks breaking the other. It doesn't quite define the system's shape (b7-b9), but it's more than a localized tax because the coupling tends to spread and affect refactoring velocity broadly.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field states that developers wrongly believe inappropriate intimacy only matters across module boundaries, and that proximity justifies tight coupling. This is a genuine and common conceptual trap — developers who co-locate related classes often assume direct internal access is acceptable. It's a well-documented gotcha but doesn't quite contradict how similar concepts work elsewhere (t7), so t5 is appropriate.
Also Known As
TL;DR
Explanation
Inappropriate Intimacy occurs when a class reaches into the internal details of another — reading private fields via getters that expose implementation, or worse, when two classes each rely on each other's internals (bidirectional dependency). This creates fragile code where changing one class's internals forces changes in the other. Remedies include moving methods to the class that owns the data they operate on (Move Method), extracting a shared class if both classes manipulate the same data, or breaking the bidirectional relationship by introducing an interface.
Common Misconception
Why It Matters
Common Mistakes
- Accessing public properties directly instead of calling methods — changes to the property name break callers.
- Classes that call private-by-convention methods (prefixed with _) of another class.
- Circular dependencies between classes — A knows B's internals, B knows A's internals.
- Using Reflection to access private members of another class in production code — extreme intimacy.
Code Examples
// OrderService reaches into User's internals
class OrderService {
public function discountRate(User $user): float {
return $user->loyaltyPoints > 1000 ? 0.15
: ($user->memberSince < '2020-01-01' ? 0.10 : 0.0);
}
}
// Move knowledge into the class that owns the data
class User {
public function discountRate(): float {
if ($this->loyaltyPoints > 1000) return 0.15;
if ($this->memberSince < new \DateTimeImmutable('2020-01-01')) return 0.10;
return 0.0;
}
}
class OrderService {
public function discountRate(User $user): float {
return $user->discountRate(); // ask User, don't reach in
}
}