Code Organization Skill
Core Principle
Directory X contains ONLY class type X
This is the fundamental rule for code organization in this codebase.
Context (Input)
- Creating new classes and determining correct directory
- Moving classes to proper locations
- Reviewing code for organizational compliance
- Fixing organizational issues from code reviews
- Ensuring class names match their responsibilities
Task (Function)
Enforce strict code organization principles: proper directory structure, DDD naming conventions, specific variable names, type safety, SOLID principles, and PHP best practices.
Directory Type Classification
Classes MUST be in directories matching their type:
| Directory | Contains ONLY | Example |
| ------------------ | ------------------------------- | ---------------------------------- |
| Converter/ | Type converters | UlidTypeConverter |
| Transformer/ | Data transformers (DB/serial) | CustomerToArrayTransformer |
| Validator/ | Validation logic | UlidValidator |
| Builder/ | Object builders | QueryBuilder |
| Fixer/ | Data fixers/modifiers | DataFixer |
| Cleaner/ | Data cleaners/filters | DataCleaner |
| Factory/ | Object factories | CustomerFactory |
| Resolver/ | Value resolvers | CustomerUpdateScalarResolver |
| Serializer/ | Serializers/normalizers | CustomerNormalizer |
| Formatter/ | Data formatters | CustomerNameFormatter |
| Mapper/ | Data mappers | PathsMapper |
| Provider/ | Data/service providers | TimestampProvider |
| Processor/ | API Platform processors | CreateCustomerProcessor |
| EventListener/ | Event listeners (Symfony) | QueryParameterValidationListener |
| EventSubscriber/ | Event subscribers (Symfony/App) | SendEmailOnCustomerCreated |
DDD Naming Patterns
By Layer and Type
| Layer | Class Type | Naming Pattern | Example |
| ------------------ | ------------------ | ------------------------------------ | --------------------------------- |
| Domain | Entity | {EntityName}.php | Customer.php |
| | Value Object | {ConceptName}.php | Email.php, Money.php |
| | Domain Event | {Entity}{PastTenseAction}.php | CustomerCreated.php |
| | Repository Iface | {Entity}RepositoryInterface.php | CustomerRepositoryInterface.php |
| | Exception | {SpecificError}Exception.php | InvalidEmailException.php |
| Application | Command | {Action}{Entity}Command.php | CreateCustomerCommand.php |
| | Command Handler | {Action}{Entity}Handler.php | CreateCustomerHandler.php |
| | Event Subscriber | {Action}On{Event}.php | SendEmailOnCustomerCreated.php |
| | DTO | {Entity}{Type}.php | CustomerInput.php |
| | Processor | {Action}{Entity}Processor.php | CreateCustomerProcessor.php |
| | Transformer | {From}To{To}Transformer.php | CustomerToArrayTransformer.php |
| Infrastructure | Repository | {Technology}{Entity}Repository.php | MySQLCustomerRepository.php |
| | Doctrine Type | {ConceptName}Type.php | UlidType.php |
| | Bus Implementation | {Framework}{Type}Bus.php | SymfonyCommandBus.php |
Directory Structure by Layer
src/{Context}/
├── Application/
│ ├── Command/ ← Commands
│ ├── CommandHandler/ ← Command Handlers
│ ├── EventSubscriber/ ← Event Subscribers
│ ├── DTO/ ← Data Transfer Objects
│ ├── Processor/ ← API Platform Processors
│ ├── Transformer/ ← Data Transformers
│ ├── Validator/ ← Validators
│ ├── Converter/ ← Type Converters
│ ├── Resolver/ ← Value Resolvers
│ ├── Factory/ ← Factories
│ ├── Builder/ ← Builders
│ ├── Formatter/ ← Formatters
│ └── MutationInput/ ← GraphQL Mutation Inputs
├── Domain/
│ ├── Entity/ ← Entities & Aggregates
│ ├── ValueObject/ ← Value Objects
│ ├── Event/ ← Domain Events
│ ├── Repository/ ← Repository Interfaces
│ └── Exception/ ← Domain Exceptions
└── Infrastructure/
├── Repository/ ← Repository Implementations
├── DoctrineType/ ← Custom Doctrine Types
├── EventSubscriber/ ← Infrastructure Event Subscribers
├── EventListener/ ← Symfony Event Listeners
└── Bus/ ← Message Bus Implementations
Verification Checklist
When creating or reviewing a class, verify:
- ✅ Class Type Matches Directory (Directory X contains ONLY class type X)
- Example:
UlidValidatorinValidator/, NOTTransformer/
- Example:
- ✅ Class Name Follows DDD Pattern for its type
- ✅ Namespace Matches Directory Structure exactly
- ✅ Class Name Reflects Actual Functionality
- ✅ Correct Layer (Domain/Application/Infrastructure)
- ✅ Domain Layer Has NO Framework Imports (Symfony/Doctrine/API Platform)
- ✅ Variable Names Are Specific (not vague)
- ✅
$typeConverter,$scalarResolver(specific) - ❌
$converter,$resolver(too vague)
- ✅
- ✅ Parameter Names Match Actual Types
- ✅
mixed $valuewhen accepts any type - ❌
string $binarywhen accepts mixed
- ✅
- ✅ No "Helper" or "Util" Classes (extract specific responsibilities)
PHP Best Practices
Required Patterns
- ✅ Constructor property promotion
- ✅ Inject ALL dependencies (no default instantiation)
- ✅ Use
readonlywhen appropriate - ✅ Use
finalfor classes that shouldn't be extended - ✅ No static methods (except named constructors like
create(),from())
Anti-Patterns (Forbidden)
- ❌ Helper/Util classes - Extract specific responsibilities
- ❌ Default instantiation in constructors - Inject dependencies
- ❌ Vague variable names - Be specific
- ❌ Namespace mismatches - Must match directory structure
Factory Pattern (Maintainability & Flexibility)
Use factories when creating typed classes with dependencies or configuration
When Factories Are REQUIRED (Production Code)
- Objects with injected dependencies (timestamp providers, config, etc.)
- Objects requiring complex construction logic
- Objects needing different implementations per environment
- Objects created from external input (DTOs, metrics, etc.)
When Factories Are OPTIONAL (Tests)
- Tests can instantiate objects directly for simplicity
- Test-specific factories can be created for reusable fixtures
Factory Benefits
- ✅ Centralized object creation logic
- ✅ Easy to inject different implementations
- ✅ Configuration changes don't affect consumers
- ✅ Single place for validation/transformation
- ✅ Enables dependency injection for complex objects
Example: Bad vs Good
// ❌ BAD: Direct instantiation with configuration
public function emit(BusinessMetric $metric): void
{
$timestamp = (int)(microtime(true) * 1000);
$payload = new EmfPayload(
new EmfAwsMetadata($timestamp, new EmfCloudWatchMetricConfig(...)),
new EmfDimensionValueCollection(...),
new EmfMetricValueCollection(...)
);
$this->logger->info($payload);
}
// ✅ GOOD: Factory handles complexity
public function emit(BusinessMetric $metric): void
{
$payload = $this->payloadFactory->createFromMetric($metric);
$this->logger->info($payload);
}
Factory Naming Convention
{ObjectName}Factory- creates{ObjectName}instances- Location: Same namespace as the object being created
- Example:
EmfPayloadFactorycreatesEmfPayload
Type Safety: Classes Over Arrays
Prefer typed classes and collections over arrays for structured data
Arrays lack type safety and self-documentation. Use concrete classes instead.
Array vs Class Comparison
| Pattern | Bad (Array) | Good (Class) |
| ------------- | ----------------------------------------- | ------------------------------------------- |
| Configuration | ['endpoint' => 'X', 'operation' => 'Y'] | new EndpointOperationDimensions('X', 'Y') |
| Return data | return ['name' => $n, 'value' => $v] | return new MetricData($n, $v) |
| Method params | function emit(array $metrics) | function emit(MetricCollection $metrics) |
| Events data | ['type' => 'created', 'id' => $id] | new CustomerCreatedEvent($id) |
Benefits of Typed Classes
- ✅ IDE autocompletion and refactoring support
- ✅ Static analysis catches type errors
- ✅ Self-documenting code
- ✅ Encapsulation (validation in constructor)
- ✅ Single Responsibility
- ✅ Open/Closed principle (extend via new classes)
Collection Pattern
// ❌ BAD: Array of arrays
$metrics = [
['name' => 'OrdersPlaced', 'value' => 1],
['name' => 'OrderValue', 'value' => 99.99],
];
// ✅ GOOD: Typed collection of objects
$metrics = new MetricCollection(
new OrdersPlacedMetric(value: 1),
new OrderValueMetric(value: 99.99)
);
When Arrays ARE Acceptable
- Simple key-value maps for serialization output (
toArray()methods) - Framework integration points requiring arrays
- Temporary internal data within a single method
Cross-Cutting Concerns Pattern
Use event subscribers for cross-cutting concerns (metrics, logging), NOT direct injection into handlers
Anti-Pattern: Metrics in Command Handler
// ❌ WRONG: Metrics in command handler
final class CreateCustomerHandler
{
public function __construct(
private CustomerRepository $repository,
private BusinessMetricsEmitterInterface $metrics // Wrong place!
) {}
public function __invoke(CreateCustomerCommand $cmd): void
{
$customer = Customer::create(...);
$this->repository->save($customer);
$this->metrics->emit(new CustomersCreatedMetric()); // Violates SRP
}
}
Correct Pattern: Dedicated Event Subscriber
// ✅ CORRECT: Clean command handler
final class CreateCustomerHandler
{
public function __construct(
private CustomerRepository $repository,
private EventBusInterface $eventBus
) {}
public function __invoke(CreateCustomerCommand $cmd): void
{
$customer = Customer::create(...);
$this->repository->save($customer);
$this->eventBus->publish(...$customer->pullDomainEvents());
// Metrics subscriber handles emission
}
}
// ✅ CORRECT: Metrics in dedicated subscriber
final class CustomerCreatedMetricsSubscriber implements DomainEventSubscriberInterface
{
public function __invoke(CustomerCreatedEvent $event): void
{
// Error handling is automatic via DomainEventMessageHandler.
// Subscribers are executed in async workers - failures are logged + emit metrics.
// This ensures observability never breaks the main request (AP from CAP).
$this->metricsEmitter->emit($this->metricFactory->create());
}
}
Common Issues and Fixes
Issue 1: Class in Wrong Type Directory
❌ WRONG:
src/Shared/Infrastructure/Transformer/UlidValidator.php
✅ CORRECT:
src/Shared/Infrastructure/Validator/UlidValidator.php
# Fix:
mv src/Shared/Infrastructure/Transformer/UlidValidator.php \
src/Shared/Infrastructure/Validator/UlidValidator.php
# Update namespace and all imports
Issue 2: Vague Variable Names
❌ WRONG:
private UlidTypeConverter $converter; // Converter of what?
✅ CORRECT:
private UlidTypeConverter $typeConverter; // Specific!
Issue 3: Misleading Parameter Names
❌ WRONG:
public function fromBinary(mixed $binary): Ulid // Accepts mixed, not just binary
✅ CORRECT:
public function fromBinary(mixed $value): Ulid // Accurate!
Issue 4: Helper/Util Classes
❌ WRONG:
class CustomerHelper {
public function validateEmail() {}
public function formatName() {}
public function convertData() {}
}
✅ CORRECT: Extract specific responsibilities
- CustomerEmailValidator (Validator/)
- CustomerNameFormatter (Formatter/)
- CustomerDataConverter (Converter/)
Issue 5: Namespace Mismatch
❌ WRONG:
// File: src/Shared/Infrastructure/Validator/UlidValidator.php
namespace App\Shared\Infrastructure\Transformer; // Mismatch!
✅ CORRECT:
// File: src/Shared/Infrastructure/Validator/UlidValidator.php
namespace App\Shared\Infrastructure\Validator; // Matches directory!
Decision Tree: Where Does It Belong?
What does the class DO?
├─ Converts between types (string ↔ object)? → Converter/
├─ Transforms for DB/serialization? → Transformer/
├─ Validates values? → Validator/
├─ Builds/constructs objects? → Builder/
├─ Fixes/modifies data? → Fixer/
├─ Cleans/filters data? → Cleaner/
├─ Creates complex objects? → Factory/
├─ Resolves/determines values? → Resolver/
├─ Normalizes/serializes? → Serializer/
├─ Formats data for display? → Formatter/
├─ Maps data between structures? → Mapper/
└─ Something else? → Define specific responsibility!
Verification Commands
# Check namespace consistency
make phpcsfixer
make psalm
# Find organizational issues
grep -r "class.*Helper" src/ # Find Helper classes
grep -r "class.*Util" src/ # Find Util classes
grep -r "private.*\$converter;" src/ # Find vague names
# Verify architecture compliance
make deptrac # Must show 0 violations
Constraints (Never Do This)
NEVER:
- Place class in wrong type directory (violates "Directory X contains ONLY class type X")
- Allow Domain layer to import framework code (Symfony/Doctrine/API Platform)
- Use vague variable names (
$converter,$resolver- be specific!) - Create "Helper" or "Util" classes (extract specific responsibilities)
- Allow namespace to mismatch directory structure
- Use arrays for structured data when typed classes would be appropriate
- Inject cross-cutting concerns (metrics, logging) into command handlers
- Create complex objects directly without factories in production code
ALWAYS:
- Verify "Directory X contains ONLY class type X" principle
- Use specific variable names (
$typeConverter, not$converter) - Use accurate parameter names (match actual types)
- Ensure namespace matches directory structure exactly
- Extract specific responsibilities from Helper/Util classes
- Prefer typed classes over arrays for structured data
- Use collections instead of arrays of objects
- Use event subscribers for cross-cutting concerns
- Use factories for complex object creation in production code
Related Skills
- code-review: References this skill for organization verification during PR reviews
- implementing-ddd-architecture: DDD patterns and layer structure
- deptrac-fixer: Fixes architectural boundary violations
- quality-standards: Maintains overall code quality metrics
Related Documentation
See reference/troubleshooting.md for detailed troubleshooting and examples/organization-fixes.md for real-world examples.
微信扫一扫