Add Doctrine Collection Generic Types¶
Priority: 🟠 P1 - HIGH (Quick Win) Status: Planning Related Analysis: phpstan-rector-analysis-overview.md
Problem Statement¶
69 PHPStan errors for missing generic type specifications on Doctrine Collections.
Current Issue¶
// ❌ Current - No generic type
public function getMarkets(): Collection
{
return $this->markets;
}
// ✅ Desired - With generic type
/**
* @return Collection<int, Market>
*/
public function getMarkets(): Collection
{
return $this->markets;
}
Impact¶
- Type Safety: PHPStan can't verify collection contents
- IDE Support: No autocomplete for collection items
- Maintainability: Unclear what collections contain
- Refactoring: Harder to refactor safely
Guideline Violations¶
- Type Safety: Missing type information
- Documentation: Unclear data structures
Root Cause¶
Doctrine Collections are generic but PHPDocs don't specify element types. This is a common issue in Doctrine codebases that has accumulated over time.
Affected Entities¶
Based on PHPStan output, affected entities likely include:
AffiliateProgram- markets collectionMarket- various relationshipsCoffeeBean- multiple collections (flavors, regions, etc.)Roaster- coffee beans, regionsCountry- regions collectionRegion- coffee beans, roastersSpecies- varietiesVariety- coffee beansProcessingMethod- coffee beansFlavorWheelNode- children nodes- All other entities with relationships
Proposed Solution¶
Use Rector to automatically add @return docblocks with generic types to all Doctrine Collection getters.
Rector Rule¶
CompleteReturnDocblockFromToManyRector - Already running and suggesting changes!
This rule:
- Detects Doctrine
@OneToManyand@ManyToManyrelationships - Analyzes target entity from relationship mapping
- Adds
@return Collection<int, EntityType>docblock - Safe and reliable
Implementation Plan¶
Step 1: Backup Current State¶
git status # Ensure clean working directory
git checkout -b type-safety/doctrine-collection-generics
Step 2: Run Rector (Dry Run First)¶
Step 3: Review Changes¶
- Verify changes are adding docblocks only
- Check that entity types are correct
- Ensure no breaking changes
Step 4: Apply Changes¶
Step 5: Run PHPStan¶
Expected result: ~69 fewer errors!
Step 6: Manual Cleanup¶
Review and fix any edge cases:
- Collections with custom keys (not int)
- Collections with union types
- Collections with interfaces
Step 7: Commit¶
git add .
git commit -m "Add generic types to Doctrine Collection getters
- Add @return Collection<int, Entity> docblocks to all entity getters
- Applied via Rector CompleteReturnDocblockFromToManyRector
- Fixes 69 PHPStan missingType.generics errors
- Improves IDE support and type safety"
Example Changes¶
Before¶
// Entity/Market.php
#[ORM\OneToMany(
mappedBy: 'market',
targetEntity: AffiliateProgram::class
)]
private Collection $affiliatePrograms;
public function getAffiliatePrograms(): Collection
{
return $this->affiliatePrograms;
}
After¶
// Entity/Market.php
#[ORM\OneToMany(
mappedBy: 'market',
targetEntity: AffiliateProgram::class
)]
private Collection $affiliatePrograms;
/**
* @return Collection<int, AffiliateProgram>
*/
public function getAffiliatePrograms(): Collection
{
return $this->affiliatePrograms;
}
Edge Cases to Handle¶
Case 1: Collections with String Keys¶
/**
* @return Collection<string, Country>
*/
public function getCountriesByCode(): Collection
{
// Indexed by country code
}
Action: Manually update if Rector uses wrong key type
Case 2: Collections with Union Types¶
/**
* @return Collection<int, CoffeeBean|DraftCoffeeBean>
*/
public function getBeans(): Collection
{
// Could be either type
}
Action: Manually add if such cases exist
Case 3: Collections of Interfaces¶
/**
* @return Collection<int, HasTimestamps>
*/
public function getTimestampedEntities(): Collection
{
// Collection of interface implementations
}
Action: Rector should handle, verify correctness
Testing Strategy¶
Automated Testing¶
- Run existing test suite - should pass unchanged
- Run PHPStan - errors should decrease by ~69
- No functional changes - tests verify correctness
Manual Testing¶
- Check IDE autocomplete in collection iteration
- Verify PHPStan understands collection contents
- Test that no new errors introduced
Verification Checklist¶
- [ ] All entity collection getters have docblocks
- [ ] Generic types match relationship mappings
- [ ] PHPStan errors reduced
- [ ] No new PHPStan errors
- [ ] All tests pass
- [ ] IDE autocomplete works
Success Criteria¶
- All Doctrine Collection return types have generic type hints
- PHPStan
missingType.genericserrors reduced from 69 to <5 - No functional changes (tests still pass)
- Improved IDE autocomplete
- Better type safety for refactoring
Risk Assessment¶
Very Low Risk:
- Docblock-only changes
- No runtime impact
- Rector rule is battle-tested
- Easy to revert if needed
Mitigation:
- Dry run first
- Review all changes
- Run full test suite
- Can revert commit if issues
Estimated Effort¶
Total: 2-3 hours
- Backup and branch creation: 5 min
- Run Rector dry run and review: 30 min
- Apply Rector: 5 min
- Manual cleanup of edge cases: 1 hour
- Run PHPStan and verify: 15 min
- Run test suite: 30 min
- Commit and documentation: 15 min
Dependencies¶
None - can be done immediately
Follow-up Tasks¶
After completing this:
- Add generic types to custom collection classes (if any)
- Consider adding PHPStan rule to enforce generics on new code
- Update coding standards to require generic types
- Add to CI/CD to prevent regression
Notes¶
- This is a quick win - high impact, low effort
- Rector automates 95% of the work
- Should be done BEFORE entity refactoring
- Complements
simplify-domain-entities.mdplan - May reveal type mismatches in existing code (good thing!)
Related Issues¶
- Helps with DTO refactoring (
refactor-dto-parameter-lists.md) - Supports entity simplification (
simplify-domain-entities.md) - Reduces overall PHPStan error count significantly