Awesome-claude-code check-lazy-loading
Analyzes PHP code for lazy loading issues. Detects premature loading, missing pagination, inappropriate eager loading, infinite scroll problems.
install
source · Clone the upstream repo
git clone https://github.com/dykyi-roman/awesome-claude-code
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/dykyi-roman/awesome-claude-code "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/check-lazy-loading" ~/.claude/skills/dykyi-roman-awesome-claude-code-check-lazy-loading && rm -rf "$T"
manifest:
skills/check-lazy-loading/SKILL.mdsource content
Lazy Loading Analysis
Analyze PHP code for lazy loading patterns and issues.
Detection Patterns
1. Premature Loading
// PREMATURE: Loading before needed public function getOrder(int $id): Order { $order = $this->repository->find($id); $items = $order->getItems(); // Loads immediately $customer = $order->getCustomer(); // Loads immediately return $order; // Items and customer may not be used } // BETTER: Let caller decide public function getOrder(int $id): Order { return $this->repository->find($id); // Items load only when accessed }
2. Missing Pagination
// NO PAGINATION: Loads entire table public function listUsers(): array { return $this->userRepository->findAll(); } // NO PAGINATION: API returns everything public function getProducts(): JsonResponse { $products = $this->repository->findBy(['active' => true]); return new JsonResponse($products); // Could be 100K products } // WITH PAGINATION: public function listUsers(int $page = 1, int $limit = 20): array { return $this->repository->findBy( [], ['createdAt' => 'DESC'], $limit, ($page - 1) * $limit ); }
3. Inappropriate Eager Loading
// OVER-EAGER: Loading unused relations $qb = $em->createQueryBuilder() ->select('u', 'o', 'p', 'c', 'a') ->from(User::class, 'u') ->join('u.orders', 'o') ->join('o.products', 'p') ->join('u.comments', 'c') ->join('u.addresses', 'a') ->where('u.id = :id'); // Only needed user name // RIGHT-SIZED: Load what's needed $qb = $em->createQueryBuilder() ->select('u.name') ->from(User::class, 'u') ->where('u.id = :id');
4. Infinite Scroll Issues
// SLOW: Offset-based pagination degrades public function loadMore(int $offset): array { return $this->repository->findBy( [], ['id' => 'DESC'], 20, $offset // Slow when offset is large ); } // BETTER: Cursor-based pagination public function loadMore(?int $lastId = null): array { $qb = $this->createQueryBuilder('p') ->orderBy('p.id', 'DESC') ->setMaxResults(20); if ($lastId !== null) { $qb->where('p.id < :lastId') ->setParameter('lastId', $lastId); } return $qb->getQuery()->getResult(); }
5. Lazy Loading in Serialization
// PROBLEM: Lazy loading during JSON encode public function getOrder(int $id): JsonResponse { $order = $this->repository->find($id); return new JsonResponse($order); // Serializer may trigger lazy loading of all relations } // FIXED: Explicit DTO or groups public function getOrder(int $id): JsonResponse { $order = $this->repository->find($id); return $this->json($order, 200, [], ['groups' => ['order:read']]); }
6. Count Without Data
// INEFFICIENT: Load all to count $users = $this->repository->findBy(['status' => 'active']); $count = count($users); // Loaded all users just to count // EFFICIENT: COUNT query $count = $this->repository->count(['status' => 'active']); // EFFICIENT: DQL count $count = $em->createQuery( 'SELECT COUNT(u) FROM User u WHERE u.status = :status' )->setParameter('status', 'active') ->getSingleScalarResult();
7. Exists Check Loading Full Entity
// INEFFICIENT: Load entity to check existence $user = $this->repository->find($id); if ($user !== null) { // User exists } // EFFICIENT: EXISTS query or count $exists = $this->repository->count(['id' => $id]) > 0; // EFFICIENT: DQL $exists = (bool) $em->createQuery( 'SELECT 1 FROM User u WHERE u.id = :id' )->setParameter('id', $id) ->setMaxResults(1) ->getOneOrNullResult();
8. Loading for Single Property
// INEFFICIENT: Load full entity for one field $user = $this->repository->find($id); $email = $user->getEmail(); // EFFICIENT: Select only needed field $email = $em->createQuery( 'SELECT u.email FROM User u WHERE u.id = :id' )->setParameter('id', $id) ->getSingleScalarResult(); // EFFICIENT: Partial object $user = $em->createQuery( 'SELECT partial u.{id, email} FROM User u WHERE u.id = :id' )->setParameter('id', $id) ->getSingleResult();
Grep Patterns
# findAll usage Grep: "->findAll\(\)" --glob "**/*.php" # count(findBy) Grep: "count\(.*->find" --glob "**/*.php" # Large offset Grep: "OFFSET\s+\$|setFirstResult\(" --glob "**/*.php" # Missing limit Grep: "findBy\([^)]+\)\s*;" --glob "**/*.php"
Pagination Patterns
Offset-Based (Simple, Slower for Large Offsets)
$results = $repository->findBy( $criteria, ['id' => 'DESC'], $limit, $offset );
Cursor-Based (Efficient, Consistent)
$qb = $this->createQueryBuilder('e') ->where('e.id < :cursor') ->setParameter('cursor', $cursor) ->orderBy('e.id', 'DESC') ->setMaxResults($limit);
Keyset Pagination (Most Efficient)
$qb = $this->createQueryBuilder('e') ->where('(e.createdAt, e.id) < (:createdAt, :id)') ->setParameters([ 'createdAt' => $lastCreatedAt, 'id' => $lastId, ]) ->orderBy('e.createdAt', 'DESC') ->addOrderBy('e.id', 'DESC') ->setMaxResults($limit);
Severity Classification
| Pattern | Severity |
|---|---|
| Missing pagination on list endpoint | 🔴 Critical |
| Load all to count | 🟠 Major |
| Large offset pagination | 🟠 Major |
| Full entity for single field | 🟡 Minor |
| Over-eager loading | 🟡 Minor |
Output Format
### Lazy Loading Issue: [Description] **Severity:** 🔴/🟠/🟡 **Location:** `file.php:line` **Type:** [Missing Pagination|Premature Loading|...] **Issue:** [Description of the lazy loading problem] **Code:** ```php // Current code
Fix:
// Optimized code
Improvement: Data loaded: 10,000 records → 20 records per page Memory: 50 MB → 1 MB