From acc
Detects N+1 query problems in PHP code including queries in loops, missing eager loading, lazy loading abuse, and relationship traversal issues.
How this skill is triggered — by the user, by Claude, or both
Slash command
/acc:detect-n-plus-oneThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Analyze PHP code for N+1 query problems that cause excessive database queries.
Analyze PHP code for N+1 query problems that cause excessive database queries.
// N+1: Query for each user
$users = $userRepository->findAll();
foreach ($users as $user) {
$orders = $orderRepository->findByUser($user); // Query per iteration
// ...
}
// N+1: Doctrine lazy loading in loop
foreach ($users as $user) {
foreach ($user->getOrders() as $order) { // Lazy loads per user
echo $order->getTotal();
}
}
// N+1: No JOIN/eager loading
$orders = $orderRepository->findAll();
foreach ($orders as $order) {
echo $order->getCustomer()->getName(); // Lazy loads customer
echo $order->getProduct()->getPrice(); // Lazy loads product
}
// FIXED: Eager load with DQL
$dql = "SELECT o, c, p FROM Order o
JOIN o.customer c
JOIN o.product p";
// N+1: Multiple levels of lazy loading
foreach ($departments as $department) {
foreach ($department->getEmployees() as $employee) { // N queries
foreach ($employee->getProjects() as $project) { // N*M queries
echo $project->getName();
}
}
}
// N+1: count() triggers query each time
foreach ($categories as $category) {
echo $category->getProducts()->count(); // Query per category
}
// N+1: filter() may not be optimized
foreach ($users as $user) {
$activeOrders = $user->getOrders()->filter(
fn($o) => $o->isActive()
); // May load all orders first
}
// N+1: Laravel Eloquent
$posts = Post::all();
foreach ($posts as $post) {
echo $post->author->name; // Query per post
}
// FIXED: Eager loading
$posts = Post::with('author')->get();
$posts = Post::with(['author', 'comments.user'])->get();
// N+1: External API pattern
foreach ($products as $product) {
$price = $pricingApi->getPrice($product->getSku()); // API call per product
}
// FIXED: Batch API call
$skus = array_map(fn($p) => $p->getSku(), $products);
$prices = $pricingApi->getPrices($skus);
# Query methods inside foreach
Grep: "foreach.*\{[^}]*->find|foreach.*\{[^}]*->query|foreach.*\{[^}]*Repository" --glob "**/*.php"
# Getter calls that might lazy load
Grep: "foreach.*->get\w+\(\)" --glob "**/*.php"
# count() in loop
Grep: "foreach.*->count\(\)" --glob "**/*.php"
# Eloquent without with()
Grep: "::all\(\)|::get\(\)|::find\(" --glob "**/*.php"
// Check for LAZY fetch mode (default)
#[ManyToOne(fetch: 'LAZY')] // N+1 risk
private ?Customer $customer;
// Should use EAGER for frequently accessed
#[ManyToOne(fetch: 'EAGER')]
private ?Customer $customer;
// Or use DQL with JOIN
$qb->select('o', 'c')
->from(Order::class, 'o')
->join('o.customer', 'c');
// DQL JOIN
$query = $em->createQuery(
'SELECT u, o, p
FROM User u
LEFT JOIN u.orders o
LEFT JOIN o.products p'
);
// Query Builder
$qb = $em->createQueryBuilder()
->select('u', 'o', 'p')
->from(User::class, 'u')
->leftJoin('u.orders', 'o')
->leftJoin('o.products', 'p');
// Load all related entities at once
$userIds = array_map(fn($u) => $u->getId(), $users);
$orders = $orderRepository->findByUserIds($userIds);
$ordersByUser = [];
foreach ($orders as $order) {
$ordersByUser[$order->getUserId()][] = $order;
}
foreach ($users as $user) {
$userOrders = $ordersByUser[$user->getId()] ?? [];
}
// Instead of N count queries
// SELECT COUNT(*) FROM products WHERE category_id = 1
// SELECT COUNT(*) FROM products WHERE category_id = 2
// ...
// Use single aggregation query
$counts = $em->createQuery(
'SELECT c.id, COUNT(p) as cnt
FROM Category c
LEFT JOIN c.products p
GROUP BY c.id'
)->getResult();
| Pattern | Severity | Queries |
|---|---|---|
| Query in loop (large dataset) | 🔴 Critical | O(n) |
| Nested relationship traversal | 🔴 Critical | O(n*m) |
| count() in loop | 🟠 Major | O(n) |
| Single lazy load | 🟡 Minor | +1 |
### N+1 Query: [Description]
**Severity:** 🔴/🟠/🟡
**Location:** `file.php:line`
**Estimated Queries:** N + 1 (where N = number of items)
**Issue:**
[Description of the N+1 pattern]
**Code:**
```php
// Code with N+1 problem
Fix:
// With eager loading
Query Reduction: Before: 101 queries (1 + 100 items) After: 1-2 queries
## When This Is Acceptable
- **Small fixed collections** — Iterating over <5 items with individual queries may be simpler than eager loading
- **CLI/worker context** — Background jobs where latency is less critical than memory
- **Cached results** — Queries inside loops that hit cache (Redis/in-memory) instead of database
### False Positive Indicators
- Loop iterates over a constant or config-defined small set
- Query result is wrapped in cache decorator
- Code is in a console command or queue worker
npx claudepluginhub dykyi-roman/awesome-claude-code --plugin accDetects N+1 query patterns in custom WordPress theme/plugin PHP code using High/Medium/Low confidence tiers. Skips third-party plugins and provides rewrite suggestions with extracted variable names.
Analyzes PHP code for lazy loading issues. Detects premature loading, missing pagination, inappropriate eager loading, infinite scroll problems.
Detects and resolves N+1 query patterns in ORMs and GraphQL resolvers using eager loading, DataLoader batching, and query count monitoring.