| name | acc-check-leaky-abstractions |
| description | Detects leaky abstractions in PHP code. Identifies implementation details exposed in interfaces, concrete returns from abstract methods, framework leakage into domain, and infrastructure concerns in application layer. |
Leaky Abstractions Detector
Overview
This skill analyzes PHP codebases for leaky abstractions — situations where implementation details "leak" through interface boundaries, violating encapsulation and creating tight coupling.
Leaky Abstraction Types
| Type | Description | Severity |
|---|
| Interface Leakage | Implementation details in interface | CRITICAL |
| Framework Leakage | Framework types in domain/application | CRITICAL |
| Return Type Leakage | Concrete types returned from abstractions | WARNING |
| Parameter Leakage | Implementation-specific parameters | WARNING |
| Exception Leakage | Infrastructure exceptions crossing boundaries | WARNING |
| Dependency Leakage | Inner dependencies exposed | INFO |
Detection Patterns
Phase 1: Interface Leakage
Grep: "Collection|ArrayCollection|PersistentCollection" --glob "**/Domain/**/*Interface.php"
Grep: "Doctrine\\\\|Illuminate\\\\|Symfony\\\\" --glob "**/Domain/**/*Interface.php"
Grep: "Redis|Memcached|Elasticsearch|Guzzle|Http" --glob "**/Domain/**/*Interface.php"
Grep: "QueryBuilder|EntityManager|Connection|PDO" --glob "**/Domain/**/*RepositoryInterface.php"
Grep: "#\\[ORM\\\\|@ORM\\\\|@Entity|@Table" --glob "**/Domain/**/*Interface.php"
Example Violations:
interface UserRepositoryInterface
{
public function findByQuery(QueryBuilder $query): Collection;
}
interface UserRepositoryInterface
{
public function findByCriteria(UserCriteria $criteria): array;
}
Phase 2: Framework Leakage into Domain
Grep: "use Symfony\\\\Component\\\\" --glob "**/Domain/**/*.php"
Grep: "use Symfony\\\\Contracts\\\\" --glob "**/Domain/**/*.php"
Grep: "use Illuminate\\\\" --glob "**/Domain/**/*.php"
Grep: "use Doctrine\\\\ORM\\\\Mapping" --glob "**/Domain/**/*.php"
Grep: "#\\[ORM\\\\|@ORM\\\\|@Entity|@Column|@ManyToOne" --glob "**/Domain/**/*.php"
Grep: "Request|Response|HttpFoundation" --glob "**/Domain/**/*.php"
Domain Should NOT Contain:
- Framework service containers
- HTTP request/response objects
- ORM annotations (use separate mapping files)
- Framework validators
- Framework events (use domain events)
Phase 3: Return Type Leakage
Grep: "public function.*\):\s*[A-Z][a-z]+[A-Z]" --glob "**/*Interface.php"
Grep: "): Collection|): ArrayCollection" --glob "**/Domain/**/*Interface.php"
Grep: "): \?[A-Z][a-z]+\s*;|): null\|[A-Z]" --glob "**/*Interface.php"
Grep: "): Response|): JsonResponse|): View" --glob "**/Application/**/*.php"
Phase 4: Parameter Leakage
Grep: "function.*EntityManager|function.*Connection" --glob "**/Domain/**/*.php"
Grep: "function.*QueryBuilder|function.*Criteria\s*\$" --glob "**/Domain/**/*.php"
Grep: "function.*Request \$request" --glob "**/Application/**/*UseCase.php"
Grep: "function.*Request \$request" --glob "**/Application/**/*Handler.php"
Grep: "function.*Config|function.*Parameters" --glob "**/Domain/**/*.php"
Phase 5: Exception Leakage
Grep: "throw.*Doctrine\\\\|catch.*Doctrine\\\\" --glob "**/Domain/**/*.php"
Grep: "throw.*PDOException|catch.*PDOException" --glob "**/Domain/**/*.php"
Grep: "throw.*HttpException|throw.*NotFoundHttpException" --glob "**/Application/**/*.php"
Grep: "catch.*\\\\Infrastructure\\\\" --glob "**/Application/**/*.php"
Grep: "catch.*Exception" --glob "**/Infrastructure/**/*Repository.php" -A 3
Phase 6: Dependency Leakage
Grep: "__construct.*EntityManager|__construct.*Connection" --glob "**/Application/**/*.php"
Grep: "public function.*Logger|public function.*Cache" --glob "**/Domain/**/*.php"
Grep: "public function get.*\(\).*EntityManager|public function get.*\(\).*Repository" --glob "**/*.php"
Phase 7: Serialization Leakage
Grep: "JsonSerializable|jsonSerialize" --glob "**/Domain/**/*.php"
Grep: "#\\[Serializer\\\\|#\\[Groups|@Groups" --glob "**/Domain/**/*.php"
Grep: "#\\[ApiResource|#\\[ApiProperty" --glob "**/Domain/**/*.php"
Report Format
# Leaky Abstractions Report
## Summary
| Leak Type | Critical | Warning | Info |
|-----------|----------|---------|------|
| Interface Leakage | 2 | 3 | - |
| Framework Leakage | 4 | 2 | - |
| Return Type Leakage | - | 5 | 3 |
| Parameter Leakage | 1 | 4 | - |
| Exception Leakage | 2 | 3 | - |
| Dependency Leakage | - | 2 | 4 |
**Total Leaks:** 8 critical, 19 warnings, 7 info
## Critical Issues
### LEAK-001: Doctrine Collection in Interface
- **File:** `src/Domain/User/UserRepositoryInterface.php:12`
- **Issue:** ORM-specific type in domain interface
- **Code:**
```php
public function findActive(): Collection;
LEAK-002: Framework in Domain Entity
LEAK-003: HTTP Request in UseCase
Warning Issues
LEAK-004: PDOException Not Translated
- File:
src/Infrastructure/Repository/DoctrineUserRepository.php:45
- Issue: Database exception not translated to domain exception
- Code:
public function save(User $user): void
{
$this->em->persist($user);
$this->em->flush();
}
- Expected:
public function save(User $user): void
{
try {
$this->em->persist($user);
$this->em->flush();
} catch (UniqueConstraintViolationException $e) {
throw new UserAlreadyExistsException($user->email());
}
}
LEAK-005: Concrete Return Type
LEAK-006: Infrastructure Logger in Domain
Abstraction Boundaries
┌─────────────────────────────────────────────────────────────┐
│ Presentation Layer │
│ Request, Response, Controller, View │
├─────────────────────────────────────────────────────────────┤
│ Application Layer │
│ Commands, Queries, Handlers, DTOs │
│ ❌ No HTTP types, ❌ No framework services │
├─────────────────────────────────────────────────────────────┤
│ Domain Layer │
│ Entities, Value Objects, Domain Services, Interfaces │
│ ❌ No ORM, ❌ No framework, ❌ No infrastructure │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Layer │
│ Repositories, Adapters, External Services │
│ ✅ ORM, ✅ Framework, ✅ Database │
└─────────────────────────────────────────────────────────────┘
Refactoring Strategies
Interface Abstraction
| Leaky | Clean |
|---|
Collection | array or custom *Collection |
QueryBuilder | Criteria or Specification |
EntityManager | RepositoryInterface |
Request | Command / Query DTO |
Response | Return value + Responder |
Exception Translation
try {
$this->connection->execute($sql);
} catch (UniqueConstraintViolationException $e) {
throw new DuplicateEmailException($email);
} catch (\PDOException $e) {
throw new PersistenceException('Failed to save user', 0, $e);
}
Framework Independence
interface UserRepositoryInterface
{
public function findActive(): array;
}
class DoctrineUserRepository implements UserRepositoryInterface
{
public function findActive(): array
{
return $this->createQueryBuilder('u')
->where('u.active = true')
->getQuery()
->getResult();
}
}
## Quick Analysis Commands
```bash
# Detect leaky abstractions
echo "=== Framework in Domain ===" && \
grep -rn "use Doctrine\\|use Symfony\\|use Illuminate\\" --include="*.php" src/Domain/ && \
echo "=== ORM in Interfaces ===" && \
grep -rn "Collection|QueryBuilder|EntityManager" --include="*Interface.php" src/ && \
echo "=== HTTP in Application ===" && \
grep -rn "Request|Response|HttpFoundation" --include="*.php" src/Application/ && \
echo "=== Unhandled Exceptions ===" && \
grep -rn "throw.*PDO\|throw.*Doctrine" --include="*.php" src/Domain/ src/Application/
Integration
Works with:
acc-structural-auditor — Layer boundary analysis
acc-ddd-auditor — Domain purity checks
acc-create-repository — Clean repository interfaces
acc-create-anti-corruption-layer — External system isolation
References
- "The Law of Leaky Abstractions" — Joel Spolsky
- "Clean Architecture" (Robert C. Martin) — Dependency Rule
- "Domain-Driven Design" (Eric Evans) — Layered Architecture