mir-backend-php-symfonylisted
Install: claude install-skill anantbhandarkar/make-it-right
# /mir-backend-php-symfony · Make It Right (Symfony)
Bottom tier of the chain: `mir-backend` (generic gates) → `mir-backend-php` (Zend Engine runtime model) → **this** (Symfony/Doctrine library mechanics). Run the gates first; load the PHP runtime tier for the lifecycle/process model; reach for *this* at Gate 5 (design mechanics), Gate 6 (implementation), and Gate 7 review. **Runtime-level concerns (shared-nothing lifecycle, FPM sizing, Swoole/RoadRunner/FrankenPHP state bleed, opcache, pconnect, error model) live in `mir-backend-php` — not here.**
**Stack assumed:** Symfony 6/7 · Doctrine ORM 2/3 · PostgreSQL or MySQL · Symfony Messenger · API Platform (optional). If the project uses DBAL only (no ORM) or a different message bus, note the divergence before applying these.
## The Symfony/Doctrine footguns AI walks into most
### 1. Doctrine N+1 via lazy proxies — silent DB storm
Doctrine wraps unloaded associations in lazy-loading proxy objects. Accessing a proxy property (e.g. `$order->getCustomer()->getName()`) inside a loop triggers a SELECT per iteration — N orders produce N+1 queries total. AI writes this constantly because the PHP code *looks* correct.
- **Fix:** use DQL fetch joins (`JOIN FETCH`) or `findBy` with `fetch: 'EAGER'` configuration. For collections, use `Criteria` or a custom repository method with `leftJoin`/`addSelect`. In API Platform, configure `eager` fetch on the relation or use an `ApiFilter` with join.
```php
// WRONG — N+1: one SELECT per $or