acc-check-encapsulation
Analyzes PHP code for encapsulation violations. Detects public mutable state, exposed internals, Tell Don't Ask violations, getter/setter abuse, and information hiding breaches.
Best use case
acc-check-encapsulation is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Analyzes PHP code for encapsulation violations. Detects public mutable state, exposed internals, Tell Don't Ask violations, getter/setter abuse, and information hiding breaches.
Teams using acc-check-encapsulation should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/acc-check-encapsulation/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How acc-check-encapsulation Compares
| Feature / Agent | acc-check-encapsulation | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Analyzes PHP code for encapsulation violations. Detects public mutable state, exposed internals, Tell Don't Ask violations, getter/setter abuse, and information hiding breaches.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Encapsulation Analyzer
## Overview
This skill analyzes PHP codebases for encapsulation violations — situations where internal state is exposed, getters/setters replace behavior, or the "Tell, Don't Ask" principle is violated.
## Encapsulation Principles
| Principle | Description | Violation Indicator |
|-----------|-------------|---------------------|
| Information Hiding | Internal state not exposed | Public properties, many getters |
| Tell Don't Ask | Objects perform actions, not expose data | Getter chains, external decisions |
| Behavioral Richness | Objects have behavior, not just data | Anemic domain model |
| Invariant Protection | State changes validate constraints | Public setters without validation |
## Detection Patterns
### Phase 1: Public Mutable State
```bash
# Public properties (non-readonly)
Grep: "public string|public int|public float|public bool|public array|public \?" --glob "**/Domain/**/*.php"
# Public properties in entities
Grep: "public \$|public string \$|public int \$" --glob "**/Entity/**/*.php"
# Expected: private/protected or public readonly
Grep: "public readonly|private readonly|protected readonly" --glob "**/Domain/**/*.php"
```
**Violations:**
```php
// BAD: Public mutable state
class User
{
public string $email; // Can be modified externally!
public int $age; // No validation!
public array $permissions; // Collection exposed!
}
// GOOD: Encapsulated state
final class User
{
private Email $email;
private Age $age;
private PermissionCollection $permissions;
public function changeEmail(Email $newEmail): void { /* ... */ }
public function grantPermission(Permission $permission): void { /* ... */ }
}
```
### Phase 2: Getter/Setter Abuse
```bash
# Getter/setter pairs (anemic model indicator)
Grep: "public function get[A-Z][a-z]+\(\)" --glob "**/Domain/**/*.php"
Grep: "public function set[A-Z][a-z]+\(" --glob "**/Domain/**/*.php"
# Count getters vs behavior methods
# High getter ratio = anemic model
# Setters in entities (should be behavior methods)
Grep: "public function set[A-Z]" --glob "**/Entity/**/*.php"
Grep: "public function set[A-Z]" --glob "**/Aggregate/**/*.php"
# Direct property setters
Grep: "\$this->[a-z]+ = \$" --glob "**/Domain/**/*.php"
# Check if inside validated method or public setter
```
**Getter/Setter Anti-pattern:**
```php
// BAD: Anemic entity
class Order
{
public function getStatus(): string { return $this->status; }
public function setStatus(string $status): void { $this->status = $status; }
}
// External code makes decisions
if ($order->getStatus() === 'pending') {
$order->setStatus('confirmed');
}
// GOOD: Rich entity
final class Order
{
public function confirm(): void
{
if ($this->status !== OrderStatus::PENDING) {
throw new CannotConfirmOrderException();
}
$this->status = OrderStatus::CONFIRMED;
$this->recordEvent(new OrderConfirmedEvent($this->id));
}
}
```
### Phase 3: Tell Don't Ask Violations
```bash
# Getter chains (asking for data to make decisions)
Grep: "->get[A-Z][a-z]+\(\)->get[A-Z][a-z]+\(\)" --glob "**/*.php"
Grep: "if \(\$.*->get[A-Z].*->get[A-Z]" --glob "**/*.php"
# External conditionals on object state
Grep: "if \(\$[a-z]+->get[A-Z][a-z]+\(\) ===" --glob "**/*.php"
Grep: "if \(\$[a-z]+->is[A-Z][a-z]+\(\))" --glob "**/*.php"
# Switch on object state
Grep: "switch \(\$.*->get[A-Z]|match \(\$.*->get[A-Z]" --glob "**/*.php"
```
**Tell Don't Ask Pattern:**
```php
// BAD: Ask then act
if ($user->getBalance()->getAmount() >= $payment->getAmount()) {
$user->setBalance(
$user->getBalance()->subtract($payment->getAmount())
);
}
// GOOD: Tell
$user->pay($payment);
// Inside User
public function pay(Payment $payment): void
{
if (!$this->balance->canAfford($payment->amount())) {
throw new InsufficientBalanceException();
}
$this->balance = $this->balance->subtract($payment->amount());
$this->recordEvent(new PaymentMadeEvent($this->id, $payment->id()));
}
```
### Phase 4: Collection Exposure
```bash
# Returning mutable collections
Grep: "public function get[A-Z][a-z]+\(\): array" --glob "**/Entity/**/*.php" -A 3
# Check if returns internal array directly
# Doctrine collections exposed
Grep: "public function get[A-Z][a-z]+\(\): Collection" --glob "**/Domain/**/*.php"
# Array modifications outside entity
Grep: "\$.*->get[A-Z][a-z]+\(\)\[\]|array_push\(\$.*->get" --glob "**/*.php"
```
**Collection Encapsulation:**
```php
// BAD: Collection exposed
class Order
{
/** @return OrderItem[] */
public function getItems(): array
{
return $this->items; // Internal array exposed!
}
}
// External modification
$order->getItems()[] = $newItem; // Bypasses validation!
// GOOD: Collection encapsulated
final class Order
{
public function addItem(Product $product, Quantity $quantity): void
{
$this->validateCanAddItem($product);
$this->items[] = new OrderItem($product, $quantity);
$this->recalculateTotal();
}
/** @return OrderItem[] */
public function items(): array
{
return [...$this->items]; // Return copy
}
}
```
### Phase 5: Exposed Internals
```bash
# Internal state returned
Grep: "return \$this->[a-z]+;" --glob "**/Domain/**/*.php"
# Check if returning mutable objects
# Private field via reflection
Grep: "ReflectionClass|ReflectionProperty|setAccessible" --glob "**/*.php"
# Debug/dump methods exposing state
Grep: "public function toArray\(\)|public function dump\(\)|public function debug\(\)" --glob "**/Domain/**/*.php"
# Serialization exposing internals
Grep: "__serialize|__sleep|jsonSerialize" --glob "**/Domain/**/*.php"
```
### Phase 6: Constructor Injection Issues
```bash
# Too many dependencies (SRP violation indicator)
Grep: "__construct\(" --glob "**/Domain/**/*.php" -A 15
# Count parameters
# Public constructor with complex setup
Grep: "public function __construct" --glob "**/Domain/**/*.php" -A 20
# Check for business logic in constructor
# Missing factory for complex construction
Grep: "new [A-Z][a-z]+Entity\(|new [A-Z][a-z]+Aggregate\(" --glob "**/Application/**/*.php"
# Complex instantiation outside factory
```
## Report Format
```markdown
# Encapsulation Analysis Report
## Summary
| Issue Type | Critical | Warning | Info |
|------------|----------|---------|------|
| Public Mutable State | 3 | 5 | - |
| Getter/Setter Abuse | 2 | 8 | 12 |
| Tell Don't Ask | 4 | 15 | - |
| Collection Exposure | 2 | 6 | - |
| Exposed Internals | 1 | 3 | 4 |
**Encapsulation Score: 68%**
## Critical Issues
### ENC-001: Public Mutable Properties
- **File:** `src/Domain/User/Entity/User.php:12`
- **Issue:** Public properties allow external modification
- **Code:**
```php
public string $email;
public string $name;
public array $roles;
```
- **Expected:**
```php
private Email $email;
private Name $name;
private RoleCollection $roles;
public function changeEmail(Email $email): void { /* validate */ }
```
- **Skills:** `acc-create-entity`, `acc-create-value-object`
### ENC-002: Anemic Entity
- **File:** `src/Domain/Order/Entity/Order.php`
- **Issue:** 15 getters, 12 setters, 0 behavior methods
- **Code:**
```php
public function getStatus(): string { ... }
public function setStatus(string $status): void { ... }
```
- **Expected:** Replace setters with behavior methods
```php
public function confirm(): void { /* validate and transition */ }
public function ship(TrackingNumber $tracking): void { /* ... */ }
public function cancel(CancellationReason $reason): void { /* ... */ }
```
- **Skills:** `acc-create-entity`
### ENC-003: Collection Mutated Externally
- **File:** `src/Application/Service/OrderService.php:45`
- **Issue:** Adding items bypasses entity validation
- **Code:**
```php
$order->getItems()[] = $newItem;
```
- **Expected:**
```php
$order->addItem($product, $quantity);
```
## Warning Issues
### ENC-004: Tell Don't Ask Violation
- **File:** `src/Application/Handler/ConfirmOrderHandler.php:34`
- **Issue:** External logic should be in entity
- **Code:**
```php
if ($order->getStatus() === 'pending' &&
$order->getPayment()->getStatus() === 'completed') {
$order->setStatus('confirmed');
}
```
- **Expected:**
```php
$order->confirm(); // Validation inside entity
```
### ENC-005: Getter Chain
- **File:** `src/Application/Service/ReportService.php:78`
- **Issue:** Law of Demeter violation
- **Code:**
```php
$country = $user->getAddress()->getCity()->getCountry()->getName();
```
- **Refactoring Options:**
1. Add shortcut: `$user->countryName()`
2. Pass needed data: `new Report($user->address()->countryName())`
### ENC-006: Internal Array Returned
- **File:** `src/Domain/Order/Entity/Order.php:89`
- **Issue:** Internal array returned by reference
- **Code:**
```php
public function getItems(): array
{
return $this->items;
}
```
- **Expected:**
```php
/** @return OrderItem[] */
public function items(): array
{
return [...$this->items]; // Return copy
}
```
## Metrics
### Getter/Behavior Ratio
| Entity | Getters | Setters | Behavior | Ratio | Status |
|--------|---------|---------|----------|-------|--------|
| User | 8 | 5 | 3 | 4.3 | ⚠️ Poor |
| Order | 15 | 12 | 2 | 13.5 | ❌ Anemic |
| Product | 6 | 0 | 5 | 1.2 | ✅ Good |
| Payment | 4 | 2 | 4 | 1.5 | ✅ Good |
**Target:** Ratio < 2.0 (behaviors should outnumber getters)
### Public State Exposure
| Layer | Public Props | Readonly Props | Private Props |
|-------|--------------|----------------|---------------|
| Domain | 12 ❌ | 8 | 45 |
| Application | 0 | 23 | 15 |
## Refactoring Recommendations
### Immediate
1. Make all entity properties private
2. Replace setters with behavior methods
3. Return collection copies, not references
### Short-term
4. Extract Value Objects for validated data
5. Add factory methods for complex construction
6. Remove getter chains (add shortcut methods)
### Long-term
7. Review anemic entities for missing behavior
8. Consider CQRS to separate read/write models
```
## Encapsulation Patterns
### Rich Entity Example
```php
final class Order
{
private OrderId $id;
private CustomerId $customerId;
private OrderStatus $status;
private OrderItemCollection $items;
private Money $total;
private array $events = [];
public static function create(CustomerId $customerId): self
{
$order = new self();
$order->id = OrderId::generate();
$order->customerId = $customerId;
$order->status = OrderStatus::DRAFT;
$order->items = new OrderItemCollection();
$order->total = Money::zero();
$order->recordEvent(new OrderCreatedEvent($order->id));
return $order;
}
public function addItem(Product $product, Quantity $quantity): void
{
$this->assertDraft();
$item = OrderItem::create($product, $quantity);
$this->items = $this->items->add($item);
$this->recalculateTotal();
}
public function submit(): void
{
$this->assertDraft();
$this->assertHasItems();
$this->status = OrderStatus::SUBMITTED;
$this->recordEvent(new OrderSubmittedEvent($this->id));
}
// Query methods (no state exposure)
public function id(): OrderId { return $this->id; }
public function total(): Money { return $this->total; }
public function isSubmitted(): bool { return $this->status->equals(OrderStatus::SUBMITTED); }
private function assertDraft(): void
{
if (!$this->status->equals(OrderStatus::DRAFT)) {
throw new OrderNotDraftException($this->id);
}
}
}
```
## Quick Analysis Commands
```bash
# Check encapsulation
echo "=== Public Properties ===" && \
grep -rn "public string\|public int\|public array" --include="*.php" src/Domain/ | grep -v "readonly" && \
echo "=== Setter Methods ===" && \
grep -rn "public function set[A-Z]" --include="*.php" src/Domain/ && \
echo "=== Getter Chains ===" && \
grep -rn "->get[A-Z].*->get[A-Z].*->get[A-Z]" --include="*.php" src/ && \
echo "=== Tell Don't Ask ===" && \
grep -rn "if (\$.*->get[A-Z].*===\|switch (\$.*->get[A-Z]" --include="*.php" src/Application/
```
## Integration
Works with:
- `acc-detect-code-smells` — Feature Envy, Anemic Model
- `acc-structural-auditor` — DDD compliance
- `acc-create-entity` — Generate rich entities
- `acc-create-value-object` — Encapsulated value types
## References
- "Tell, Don't Ask" — Martin Fowler
- "Anemic Domain Model" — Martin Fowler
- "Object-Oriented Software Construction" (Bertrand Meyer)
- "Elegant Objects" (Yegor Bugayenko)Related Skills
acc-check-leaky-abstractions
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.
acc-check-bounded-contexts
Analyzes bounded context boundaries in DDD projects. Detects cross-context coupling, shared kernel violations, context mapping issues, and ubiquitous language inconsistencies. Generates context map diagrams and boundary recommendations.
a11y-self-check
Proactively validates Claude Code's own generated HTML/JSX/TSX output for accessibility before presenting to users. Use this skill automatically when generating UI code to ensure WCAG 2.1 AA compliance.
asyncredux-check-internet-mixin
Add the CheckInternet mixin to ensure network connectivity before action execution. Covers automatic error dialogs, combining with NoDialog for custom UI handling, and AbortWhenNoInternet for silent abort.
accessibility-design-checklist
Эксперт по accessibility дизайну. Используй для WCAG, a11y чеклистов и inclusive design.
accessibility-design-checker
Ensures designs meet accessibility requirements including WCAG compliance, color contrast, keyboard navigation, screen reader support, and focus management. Reviews designs for accessibility issues and provides recommendations.
accessibility-basic-check
Run a basic accessibility checklist for UI changes. Use when a junior developer needs quick a11y guidance.
mailcheck-automation
Automate Mailcheck tasks via Rube MCP (Composio). Always search tools first for current schemas.
ddd-check
DDD設計原則チェッカー(AIDLC ドキュメントと実装コードの一貫性を検証)
COMPLIANCE_CHECK
Apply the OpenAI SDK compliance checklist to audit files or directories and produce a Markdown report with findings and suggested fixes. Use when asked to "check compliance", "run compliance check", or "audit against OpenAI SDK rules".
check-ceph-health
Check Ceph storage health on OpenShift OCS/ODF clusters. Use when PVCs are stuck in Pending, storage provisioning fails, Ceph is degraded, OSDs are full, or cluster storage needs diagnosis.
acc-check-immutability
Analyzes PHP code for immutability violations. Checks Value Objects, Events, DTOs for readonly properties, no setters, final classes, and wither patterns. Ensures domain objects maintain invariants.