Enforce code organization principles - "Directory X contains ONLY class type X", DDD naming patterns, PHP best practices, type safety, SOLID principles, and hardcoded config extraction to .env. Use when reviewing code structure, placing classes, refactoring, fixing CI failures related to structure, or extracting hardcoded configuration values.
Standardmรครig ist der Prompt ausgewรคhlt, der zuerst die Quelle prรผft. Sie kรถnnen zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.
Quelldateien prรผfen
Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich fรผr eine Installation entscheiden.
Mit Codex oder Claude installieren Kopieren Sie diesen Prompt, fรผgen Sie ihn in Codex, Claude oder einen anderen Assistant ein und lassen Sie die Skill-Seite prรผfen und installieren.
Ein direkter Befehl รผberspringt den Prรผf-Prompt. Prรผfen Sie die Quelle, bevor Sie ihn ausfรผhren.
Enforce code organization principles - "Directory X contains ONLY class type X", DDD naming patterns, PHP best practices, type safety, SOLID principles, and hardcoded config extraction to .env. Use when reviewing code structure, placing classes, refactoring, fixing CI failures related to structure, or extracting hardcoded configuration values.
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
Fixing CI failures that stem from structural/naming issues
Extracting hardcoded config values (TTLs, timeouts, limits) to .env
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
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
Directory Creation Guardrails
NEVER create new directories autonomously โ every new class-type directory MUST follow a well-known software engineering pattern (Factory, Builder, Processor, Validator, Provider, Resolver, etc.) AND be explicitly requested/approved by the user. When in doubt, use an existing directory.
Do not invent ad-hoc class-type directories or suffixes. The following are explicitly forbidden:
Applier/, Attacher/, Enricher/ โ not well-known patterns
Service/ โ leads to anemic domain models; use specific pattern names instead (Provider, Factory, Resolver, etc.)
Any proposed new directory MUST be a well-known software engineering pattern (e.g. Factory, Builder, Strategy, Observer, Adapter, Decorator, Proxy, Iterator, Mediator, etc.) โ not an invented verb-noun.
Use existing DDD/CQRS directory types and naming patterns from this skill.
Follow DDD and CQRS strictly โ all class organization must align with established DDD layers and CQRS patterns.
โ New directories are explicit and standard, not agent-invented โ must be explicitly approved by the user
PHP Best Practices
Required Patterns
โ Constructor property promotion
โ Inject ALL dependencies (no default instantiation)
โ Use readonly when appropriate
โ Use final for classes that shouldn't be extended
โ No static methods (except named constructors like create(), from())
Anti-Patterns (Forbidden)
โ Helper/Util/Service/Manager classes - Extract specific responsibilities; Service leads to anemic domain models
โ Non-standard pattern directories - No Applier/, Attacher/, Enricher/, Augmenter/ โ use well-known patterns (Processor, Transformer, Validator, Factory, etc.)
โ Default instantiation in constructors - Inject dependencies
โ Vague variable names - Be specific
โ Namespace mismatches - Must match directory structure
โ Ad-hoc directory/class type inventions - Use established patterns only; NEVER create new directories without explicit user approval
โ Autonomous directory creation - Agent must NEVER create a new class-type directory on its own; any new directory must follow a well-known software engineering pattern and be approved by the user
โ Constructor defaults that instantiate collaborators - Inject dependencies instead of using new in __construct(...) defaults. Psalm architecture guards enforce this in src/.
โ Direct new OAuthProvider(...) in production code - Use OAuthProvider::fromString() instead. Psalm architecture guards enforce this in src/.
โ new StringableArrayNormalizer() in Doctrine types - Allowed because Doctrine types cannot use constructor DI.
โ Direct instantiation of reviewed collections/events in production code - Use dedicated factory classes such as OAuthProviderCollectionFactory, SignInEventFactory, SessionRevocationEventFactory, TwoFactorEventFactory, and RefreshTokenEventFactory. Psalm architecture guards enforce this in src/.
โ Plain json_encode/json_decode - Use Symfony SerializerInterface for serialization/deserialization. Psalm forbiddenFunctions enforce this in src/; tests are excluded.
โ Untyped array in method signatures - Always specify the array's content type via docblock (list<string>, array<string, int>) or use a typed collection class. Psalm architecture guards flag bare array type hints without generic type info in src/ (excluding DoctrineType and Collection directories).
โ Bare array, list, or iterable collections of domain objects - Use typed collection classes instead of bare arrays. Psalm architecture guards enforce this repo-wide in src/ (not just OAuth). Enforced types and their collections:
Internal storage inside collection classes may still use array.
Factory Pattern (Maintainability & Flexibility)
Avoid hardcoded new ClassName() in production source code โ use factory methods or Factory classes
Factory Methods on Value Objects
Value objects SHOULD provide static factory methods as named constructors:
// โ BAD: Direct instantiation in production code$provider = newOAuthProvider($value);
// โ GOOD: Factory method$provider = OAuthProvider::fromString($value);
Factory methods (fromString(), fromArray(), create()) are the preferred way to instantiate value objects outside of their own class. The constructor remains public for use within named constructors and tests.
Collections and domain events should follow a different rule in production code: use dedicated Factory classes instead of adding static convenience constructors just to avoid new.
When Factory Classes 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 Direct new Is ACCEPTABLE
Inside factory methods and Factory classes (that's their purpose)
In test code (simplicity over abstraction)
For framework-required patterns (e.g., throw new InvalidArgumentException())
Inside the value object's own named constructors
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
Location: Same namespace as the object being created
Example: EmfPayloadFactory creates EmfPayload
Type Safety: Classes Over Arrays
Arrays are NOT allowed for collections that already have a dedicated collection type. Use the collection class instead.
Arrays lack type safety and self-documentation. Use concrete classes instead. Current CI guards specifically block bare OAuth provider collections in production code, including iterable-based variants.
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/
โโ Creates complex objects? โ Factory/
โโ Resolves/determines values? โ Resolver/
โโ Normalizes/serializes? โ Serializer/
โโ Formats data for display? โ Formatter/
โโ Maps data between structures? โ Mapper/
โโ Provides data/cookies/context? โ Provider/
โโ Something else? โ Ask the user before creating a new directory!
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
Symfony Service Configuration: No Redundant Wiring
Do not add explicit interface aliases in services.yaml when Symfony autowiring can resolve them automatically.
Rule
When an interface has exactly one implementation in src/, Symfony autowiring automatically aliases the interface to that implementation. Do NOT add a manual alias โ it is redundant.
When an Explicit Alias IS Required
The interface has multiple implementations (e.g., UserRepositoryInterface โ CachedUserRepository vs MongoDBUserRepository)
The implementation lives outside the autowired src/ resource (e.g., a third-party bundle class)
You need to alias to a different implementation than what autowiring would pick
When an Explicit Alias is REDUNDANT (remove it)
Only one class in src/ implements the interface
Both the interface and implementation are covered by the App\: resource in services.yaml
Example
# โ REDUNDANT: Only one implementation exists โ autowiring handles thisApp\OAuth\Domain\Repository\SocialIdentityRepositoryInterface:alias:App\OAuth\Infrastructure\Repository\MongoDBSocialIdentityRepository# โ REQUIRED: Two implementations exist โ must disambiguateApp\User\Domain\Repository\UserRepositoryInterface:alias:App\User\Infrastructure\Repository\CachedUserRepository
Explicit Constructor Arguments Are Still Needed
Even when the alias is redundant, you may still need an explicit service definition for constructor arguments that autowiring cannot resolve (e.g., non-type-hinted parameters, named service references):
# โ NEEDED: $oauthRedis is a named Redis connection, not autowirableApp\OAuth\Infrastructure\Repository\RedisOAuthStateRepository:arguments:$oauthRedis:'@oauth.redis_connection'# โ NOT NEEDED: the interface alias (autowiring resolves it)# App\OAuth\Domain\Repository\OAuthStateRepositoryInterface:# alias: App\OAuth\Infrastructure\Repository\RedisOAuthStateRepository
Verification
# Check that autowiring resolves the interface correctly
docker compose exec php bin/console debug:container <InterfaceName>
# Should show "This service is a private alias for the service <Implementation>"
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
Use untyped array in method signatures โ always specify content type via docblock or use collection classes
Use array type for collections of domain/application objects โ use typed collections
Use json_encode/json_decode โ use Symfony SerializerInterface (enforced by Psalm forbiddenFunctions in src/)
Use constructor defaults that instantiate collaborators โ inject the dependency instead
Use direct new OAuthProvider(...) in production code โ use OAuthProvider::fromString()
Inject cross-cutting concerns (metrics, logging) into command handlers
Create complex objects directly without factories in production code
Add redundant interface aliases in services.yaml when autowiring resolves them
ALWAYS:
Verify "Directory X contains ONLY class type X" principle
Use specific variable names ($typeConverter, not $converter)
make phpcsfixer # Fix code style
make psalm # Verify type safety
make unit-tests # Ensure tests pass (update mocks for new constructor params)
make integration-tests # Verify runtime binding works
make ci # Full validation
CI Integration: When CI Fails
When make ci fails, consult this skill if the failure involves:
CI Failure Indicator
Code Organization Fix
Class not found / namespace mismatch
Verify namespace matches directory structure
Deptrac violation after moving class
Check layer placement (Domain/Application/Infra)
PHPInsights architecture score drop
Verify "Directory X contains ONLY class type X"
Psalm type errors after refactoring
Check that imports and namespaces were all updated
Test failures after class move
Move test file too, update test namespace + imports
Refactoring Checklist (Before Running CI)
When moving, renaming, or restructuring classes:
Class in correct directory for its type (see Decision Tree above)
Namespace matches directory structure exactly
All use imports updated in src/ and tests/
Test file moved to mirror source structure
Test namespace updated
config/services.yaml references updated (if service was explicitly configured)