| name | doctrine-performance |
| description | Optimize Doctrine ORM performance when queries are slow or memory usage spikes so that applications reduce latency and resource consumption. |
What I do
- Optimizing slow database queries
- Reducing memory consumption in batch operations
- Debugging N+1 query problems
- Handling large datasets efficiently
- Improving application response times
- Migrating complex database schemas
When to Use This Skill
When having to implement complex Doctrine queries or heavy memory consumption operations and loops.
When user asks for optimizing code or a feature depending on Doctrine queries.
Ask clarifying questions to ensure refactoring match expected result.
Core Principles
1. Query Optimization Fundamentals
Always Use Query Builder or DQL for Complex Queries
$users = $entityManager->getRepository(User::class)->findAll();
foreach ($users as $user) {
echo $user->getEmail();
}
$emails = $entityManager->createQueryBuilder()
->select('u.email')
->from(User::class, 'u')
->getQuery()
->getResult();
Avoid N+1 Queries with Proper Joins
$posts = $entityManager->getRepository(Post::class)->findAll();
foreach ($posts as $post) {
echo $post->getAuthor()->getName();
}
$posts = $entityManager->createQueryBuilder()
->select('p', 'a')
->from(Post::class, 'p')
->leftJoin('p.author', 'a')
->getQuery()
->getResult();
Use Partial Objects for Large Datasets
$result = $entityManager->createQueryBuilder()
->select('partial u.{id, email, username}')
->from(User::class, 'u')
->where('u.active = :active')
->setParameter('active', true)
->getQuery()
->getResult();
2. Memory Management
Batch Processing Pattern
$batchSize = 100;
$i = 0;
$query = $entityManager->createQuery('SELECT u FROM App\Entity\User u');
foreach ($query->toIterable() as $user) {
$user->setProcessed(true);
if (($i % $batchSize) === 0) {
$entityManager->flush();
$entityManager->clear();
}
$i++;
}
$entityManager->flush();
$entityManager->clear();
Use Iterate for Large Result Sets
$query = $entityManager->createQuery('SELECT u FROM App\Entity\User u');
$iterableResult = $query->toIterable();
foreach ($iterableResult as $row) {
$user = $row[0];
$entityManager->detach($user);
}
3. Index Optimization
Add Indexes for Frequently Queried Fields
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Index(name: 'idx_user_email', columns: ['email'])]
#[ORM\Index(name: 'idx_user_active_created', columns: ['active', 'created_at'])]
class User
{
#[ORM\Column(type: 'string', unique: true)]
private string $email;
#[ORM\Column(type: 'boolean')]
private bool $active;
#[ORM\Column(type: 'datetime')]
private \DateTimeInterface $createdAt;
}
Composite Indexes for Multi-Column Queries
#[ORM\Index(name: 'idx_status_priority_created', columns: ['status', 'priority', 'created_at'])]
class Task
{
}
4. Caching Strategies
Result Cache Configuration
$query = $entityManager->createQuery('SELECT u FROM App\Entity\User u WHERE u.role = :role')
->setParameter('role', 'admin')
->useResultCache(true, 3600, 'admin_users_cache');
$results = $query->getResult();
5. DQL Optimization Patterns
Efficient Counting
$count = count($repository->findAll());
$count = $entityManager->createQueryBuilder()
->select('COUNT(u.id)')
->from(User::class, 'u')
->getQuery()
->getSingleScalarResult();
Efficient Existence Check
$user = $repository->findOneBy(['email' => $email]);
$exists = $user !== null;
$exists = $entityManager->createQueryBuilder()
->select('COUNT(u.id)')
->from(User::class, 'u')
->where('u.email = :email')
->setParameter('email', $email)
->getQuery()
->getSingleScalarResult() > 0;
Subquery Optimization
$qb = $entityManager->createQueryBuilder();
$subQuery = $qb->select('IDENTITY(o.user)')
->from(Order::class, 'o')
->where('o.total > :minTotal')
->getDQL();
$users = $entityManager->createQueryBuilder()
->select('u')
->from(User::class, 'u')
->where("u.id IN ($subQuery)")
->setParameter('minTotal', 1000)
->getQuery()
->getResult();
Common Performance Pitfalls
1. Avoiding Unit of Work Overhead
foreach ($users as $user) {
$user->setActive(true);
$entityManager->flush();
}
foreach ($users as $user) {
$user->setActive(true);
}
$entityManager->flush();
2. Using Native SQL When Appropriate
$sql = "
UPDATE user
SET active = 1
WHERE created_at < DATE_SUB(NOW(), INTERVAL 1 YEAR)
";
$entityManager->getConnection()->executeStatement($sql);
$entityManager->clear();
Quick Reference
Most Common Optimizations:
- Add indexes to frequently queried columns
- Use joins to avoid N+1 queries
- Use batch processing with
clear() for large datasets
- Choose appropriate hydration modes
- Use partial object notation when loading specific fields
- Implement query result caching for expensive queries
- Use
EXTRA_LAZY for large collections
- Profile before optimizing
Remember
- Premature optimization is evil - profile first!
- Database-level operations are almost always faster than PHP loops
- Memory usage matters more than you think in long-running processes
- The fastest query is the one you don't execute
- Indexes speed up reads but slow down writes - balance accordingly