| name | code-organization |
| description | Ensure proper code organization with class names, directories, namespaces, and naming consistency following the principle "Directory X contains ONLY class type X". |
Code Organization Skill
This skill ensures proper code organization following the core principle: Directory X contains ONLY class type X.
When to Use This Skill
Activate this skill when:
- Creating new classes
- Refactoring existing code
- Moving classes between directories
- Code review feedback about organization
- Renaming classes or methods
Core Principle
Directory X should contain ONLY class type X
Examples:
Converter/ → Contains ONLY converters
Transformer/ → Contains ONLY transformers
Validator/ → Contains ONLY validators
Builder/ → Contains ONLY builders
Fixer/ → Contains ONLY fixers
Cleaner/ → Contains ONLY cleaners
Factory/ → Contains ONLY factories
Resolver/ → Contains ONLY resolvers
Repository-First Directory Architecture
Before proposing a source tree, creating files, or responding to review feedback about architecture:
- Inspect the current context directories, for example
find src/Core/Customer -maxdepth 4 -type d | sort.
- Inspect
deptrac.yaml collectors for directory names that are already recognized.
- Prefer existing class-type directories over new feature buckets.
- Name the class after its responsibility and place it in the matching directory.
- Avoid broad directories such as
Cache/, Policy/, Registry/, Scheduler/, Manager/, Helper/, or Service/ when an existing precise type directory fits.
For CQRS in this repository, async work can still be a command. If the work must be reusable across bounded contexts, define the generic command and worker in Shared, then add bounded-context adapters:
Shared/Application/Command/CacheRefreshCommand.php
Shared/Application/CommandHandler/CacheRefreshCommandHandler.php
Shared/Application/CommandHandler/AbstractCacheRefreshCommandHandler.php
Core/Customer/Application/CommandHandler/CustomerCacheRefreshCommandHandler.php
Core/Customer/Application/Factory/CustomerCacheRefreshCommandFactory.php
Do not invent Message or MessageHandler for async work if the same intent fits the existing Command/CommandHandler pattern. Do not invent ReadModel, Query, or QueryHandler unless the current context already uses that pattern and deptrac collects it.
Shared metrics belong under the existing observability structure, for example Shared/Application/Observability/Metric/CacheRefreshSucceededMetric.php, not a new bounded-context metric bucket when the metric represents cross-context lifecycle behavior.
Quick Reference: Where Does It Belong?
| Class Does | Belongs In | Examples |
|---|
| Converts types | Converter/ | UlidTypeConverter |
| Transforms data (DB↔PHP) | Transformer/ | UlidTransformer |
| Validates values | Validator/ | UlidValidator |
| Builds/constructs | Builder/ | ArrayResponseBuilder |
| Fixes/modifies | Fixer/ | ContentPropertyFixer |
| Cleans/filters | Cleaner/ | ArrayValueCleaner |
| Creates objects | Factory/ | UlidFactory |
| Resolves values | Resolver/ | ScalarResolver |
| Serializes/normalizes | Serializer/ | OpenApiNormalizer |
Organization Checklist
1. Class Location
✅ CORRECT:
namespace App\Shared\Infrastructure\Converter;
final class UlidTypeConverter // IS a Converter
{
public function toUlid(...): Ulid { }
public function fromBinary(...): Ulid { }
}
❌ WRONG:
namespace App\Shared\Infrastructure\Transformer;
final class UlidTypeConverter // IS a Converter, NOT a Transformer!
{
}
2. Class Name Consistency
✅ CORRECT:
UlidValidator → validates ULIDs
UlidTransformer → transforms for Doctrine
UlidTypeConverter → converts between types
CustomerUpdateScalarResolver → resolves scalar values
❌ WRONG:
UlidHelper → Too vague
UlidConverter → Not specific enough
UlidUtils → Extract to specific classes
3. Namespace Consistency
Namespace MUST match directory structure:
✅ CORRECT:
namespace App\Shared\Infrastructure\Validator;
❌ WRONG:
namespace App\Shared\Infrastructure\Transformer;
Refactoring Workflow
Step 1: Identify What the Class Does
Ask yourself:
- What is the PRIMARY responsibility of this class?
- Does it convert? →
Converter/
- Does it transform? →
Transformer/
- Does it validate? →
Validator/
- Does it build? →
Builder/
- Does it fix? →
Fixer/
- Does it clean? →
Cleaner/
- Does it resolve? →
Resolver/
Step 2: Check the Directory
Current location matches responsibility?
- ✅ YES → Class is correctly placed
- ❌ NO → Move to appropriate directory
Step 3: Update All References
-
Move the file:
mv src/Path/OldDir/Class.php src/Path/NewDir/Class.php
-
Update namespace in the file:
namespace App\Path\NewDir;
-
Update all imports:
grep -r "use.*OldDir\\ClassName" src/ tests/
sed -i 's|OldDir\\ClassName|NewDir\\ClassName|g' affected_files
-
Run quality checks:
make phpcsfixer
make psalm
make unit-tests
Step 4: Verify Consistency
Check that:
Essential Examples
Example 1: UlidValidator
Before:
namespace App\Shared\Infrastructure\Transformer;
final class UlidValidator { }
After:
namespace App\Shared\Infrastructure\Validator;
final class UlidValidator { }
Example 2: CustomerUpdateScalarResolver
Before:
namespace App\Core\Customer\Application\Factory;
final class CustomerUpdateScalarResolver // It's a RESOLVER, not a FACTORY!
{
public function resolveScalarValue(...) { }
}
After:
namespace App\Core\Customer\Application\Resolver;
final class CustomerUpdateScalarResolver
{
public function resolveScalarValue(...) { }
}
Variable and Parameter Naming
Variable Names
✅ CORRECT (Specific):
private UlidTypeConverter $typeConverter;
private CustomerUpdateScalarResolver $scalarResolver;
❌ WRONG (Vague):
private UlidTypeConverter $converter;
private CustomerUpdateScalarResolver $resolver;
Parameter Names
✅ CORRECT (Accurate):
public function toPhpValue(mixed $value): Ulid // Accepts mixed
public function fromBinary(mixed $value): Ulid // Accepts mixed despite name
❌ WRONG (Misleading):
public function fromBinary(mixed $binary): Ulid // Name suggests only binary
Common Mistakes to Avoid
-
Don't create "Helper" or "Util" classes
- These are code smells
- Extract specific responsibilities into properly named classes
-
Don't put multiple class types in one directory
- Converters don't belong in Transformer/
- Validators don't belong in Converter/
-
Don't use vague variable names
$converter → $typeConverter (be specific)
$data → $customerData (be descriptive)
-
Don't mismatch parameter names with types
- If parameter accepts
mixed, don't name it after one type
PHP Best Practices
Use Constructor Property Promotion
✅ CORRECT:
final readonly class CustomerUpdateFactory
{
public function __construct(
private CustomerRelationTransformerInterface $relationResolver,
private CustomerUpdateScalarResolver $scalarResolver,
) {
}
}
Always Inject All Dependencies
✅ CORRECT (All dependencies injected):
public function __construct(
private readonly IriReferenceMediaTypeTransformerInterface $mediaTypeTransformer
) {
}
❌ WRONG (Default instantiation):
public function __construct(
?IriReferenceMediaTypeTransformerInterface $mediaTypeTransformer = null
) {
$this->mediaTypeTransformer = $mediaTypeTransformer
?? new IriReferenceMediaTypeTransformer();
}
For complete PHP best practices, see php-best-practices.md.
Pre-Commit Checklist
Before committing code organization changes:
make phpcsfixer
make psalm
make unit-tests
grep -r "^namespace" src/ --include="*.php" | head -10
Success Criteria
✅ Directory contains only the type of class it's named for
✅ Class name accurately describes functionality
✅ Namespace matches directory structure
✅ Variable names are specific and clear
✅ Parameter names match their actual types
✅ All tests pass
✅ No Psalm errors
✅ Code style compliant
Related Skills
- quality-standards: Maintains code quality metrics
- ci-workflow: Runs comprehensive checks
- code-review: Handles PR feedback about organization
- testing-workflow: Ensures proper test coverage
Additional Resources