zenstruck/collection
A lightweight PHP collection library by Zenstruck offering fluent, immutable-style operations for arrays and iterables. Provides map/filter/reduce, grouping, sorting, slicing, and helpful utilities to work with data sets cleanly in Laravel or standalone PHP.
Installation:
composer require zenstruck/collection
Ensure your project uses PHP 8.5+ and Symfony 6.4+ (or Laravel 10+).
First Use Case:
Replace native Collection operations with zenstruck/collection for enhanced type safety and Doctrine integration:
use Zenstruck\Collection\Collection;
$users = Collection::fromArray($arrayOfUsers)
->filter(fn ($user) => $user['active'])
->map(fn ($user) => $user['name'])
->toArray();
Doctrine ORM Integration:
For Doctrine entities, use the DoctrineBridgeCollection:
use Zenstruck\Collection\DoctrineBridge\DoctrineBridgeCollection;
use Doctrine\ORM\EntityRepository;
$repository = $entityManager->getRepository(User::class);
$users = DoctrineBridgeCollection::fromEntityRepository($repository)
->filter(fn ($user) => $user->isActive())
->toArray();
Lazy Loading: Process large datasets efficiently with lazy evaluation:
$lazyUsers = Collection::fromArray($largeArrayOfUsers)
->lazy()
->filter(fn ($user) => $user['active'])
->map(fn ($user) => $user['name']);
Batch Processing: Use the built-in batch processor for chunked operations (e.g., bulk updates):
use Zenstruck\Collection\BatchProcessor;
$processor = new BatchProcessor(100); // Process 100 items at a time
$processor->process($users, fn ($batch) => $this->updateUsers($batch));
Replace Laravel’s Collection with Zenstruck\Collection\Collection for stricter typing and additional methods:
use Zenstruck\Collection\Collection;
$collection = Collection::fromArray($data)
->filter(fn ($item) => $item['status'] === 'active')
->sortBy('name')
->groupBy('department')
->mapWithKeys(fn ($group, $key) => [$key => $group->values()]);
Leverage DoctrineBridgeCollection for seamless entity operations:
use Zenstruck\Collection\DoctrineBridge\DoctrineBridgeCollection;
$users = DoctrineBridgeCollection::fromEntityRepository($userRepository)
->filter(fn ($user) => $user->getRole() === 'admin')
->with('posts') // Eager-load associations
->toArray();
Optimize memory usage for large datasets:
$lazy = Collection::fromArray($hugeDataset)
->lazy()
->filter(fn ($item) => $item['active'])
->map(fn ($item) => $item['id']);
foreach ($lazy as $id) {
// Process one item at a time
}
Process collections in chunks (e.g., for API calls or database updates):
use Zenstruck\Collection\BatchProcessor;
$processor = new BatchProcessor(50); // Batch size
$processor->process($users, fn ($batch) => $this->saveUsers($batch));
Integrate with Pagerfanta for advanced pagination:
use Zenstruck\Collection\Pagerfanta\PagerfantaAdapter;
$adapter = new PagerfantaAdapter($collection);
$pagerfanta = new \Pagerfanta(new \Pagerfanta\Adapter\ArrayAdapter($adapter->getIterator()));
Service Providers: Register the package in AppServiceProvider if extending functionality:
public function boot()
{
Collection::macro('customMethod', function () {
return $this->map(fn ($item) => strtoupper($item));
});
}
Eloquent Models: Use DoctrineBridgeCollection for query builder operations:
$query = User::query()->where('active', true);
$collection = DoctrineBridgeCollection::fromQueryBuilder($query);
DoctrineBundle 3: Enable output walkers for debugging:
# config/packages/zenstruck_collection.yaml
zenstruck_collection:
doctrine:
output_walkers: true
Dependency Injection: Bind DoctrineBridgeCollection as a service:
// src/Service/CollectionService.php
public function __construct(
private DoctrineBridgeCollectionFactory $factory
) {}
toArray() Early: Use lazy loading or generators to defer materialization.BatchProcessor sizes based on database/index performance.with() sparingly to avoid the N+1 problem; prefer join in QueryBuilder.PHP Version Requirements:
Doctrine ORM Quirks:
with() may trigger multiple queries if not used with fetchJoinColumn.DoctrineBridgeCollection is used with Doctrine’s latest QueryBuilder methods (e.g., expr()).Immutable Collections:
filter() return new collections; chain operations carefully to avoid memory leaks:
// Bad: Creates many intermediate collections
$result = $collection->filter(...)->map(...)->filter(...);
// Good: Use lazy evaluation
$result = $collection->lazy()->filter(...)->map(...);
Batch Processor Pitfalls:
try-catch:
$processor->process($items, fn ($batch) => {
try {
$this->saveBatch($batch);
} catch (\Throwable $e) {
$this->logError($e);
throw $e; // Re-throw to continue with next batch
}
});
100–1000 items.Type Safety:
array<array-key, T> for type hints. Ensure your IDE (PHPStorm, VSCode) supports PHP 8.5 generics.DoctrineBridgeCollection are strict. Cast to array if needed:
$array = $collection->toArray(); // array<array-key, mixed>
Doctrine Debugging:
config/packages/dev/doctrine.yaml:
doctrine:
dbal:
logging: true
profiler: true
DoctrineBridgeCollection::getQueryBuilder() to inspect the underlying query.Lazy Collection Issues:
LazyCollection:
$lazy = Collection::fromGenerator($generator)->lazy();
memory_get_usage() to profile lazy-loaded operations.Batch Processor:
$processor->process($items, fn ($batch) => {
$this->logger->info('Processing batch', ['count' => count($batch)]);
$this->saveBatch($batch);
});
Performance Bottlenecks:
xdebug_profile_enable() to identify slow collection operations.toArray(): Materializing large collections prematurely can cause OOM errors.Custom Collections:
Extend Zenstruck\Collection\Collection for domain-specific logic:
class UserCollection extends Collection
{
public function activeAdmins(): array
{
return $this->filter(fn ($user) => $user->isActive() && $user->isAdmin())
->values()
->toArray();
}
}
**Doctrine
How can I help you explore Laravel packages today?