Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Collection Laravel Package

php-standard-library/collection

Generic, object-oriented Vector, Map, and Set collections for PHP, available in both immutable and mutable variants. Part of the PHP Standard Library, offering a consistent, type-safe API for building and transforming collections.

View on GitHub
Deep Wiki
Context7

Getting Started

The split package is the object-oriented collection layer:

composer require php-standard-library/collection

You will usually want the companion function API and type system too. If you are adopting PSL broadly, require the umbrella package (for example php-standard-library/psl) rather than mixing individual split packages at drifting versions. Confirm the PHP requirement from composer show php-standard-library/collection; PSL v6 targets modern PHP 8.x, so do not assume PHP 8.1 compatibility without checking.

Where to look first:

  1. php-standard-library.dev for the actual API docs. The split repository README is only a signpost.
  2. The interfaces in the installed package: VectorInterface, MutableVectorInterface, MapInterface, MutableMapInterface, SetInterface, MutableSetInterface, and CollectionInterface. These define the real contract and are what you should type-hint against.
  3. The monorepo release notes. The split repository’s release files just redirect to php-standard-library/php-standard-library; that is where behaviour changes and migration notes live.

First use case — wrap an array at a service boundary, transform it with a typed object API, and convert back to an array before returning it to Laravel:

use Psl\Collection\Vector;

$users = Vector::fromArray(User::query()->get()->all());

$names = $users
    ->filter(fn (User $user): bool => $user->isActive())
    ->map(fn (User $user): string => $user->name)
    ->toArray();

return response()->json(['names' => $names]);

Build the same mental model for the other types:

use Psl\Collection\Map;
use Psl\Collection\Set;

$byId = Map::fromArray(collect($users)->keyBy('id')->all());
$user = $byId->get(42); // ?User; use at() if a missing key should throw

$seen = Set::fromArray([1, 2, 3]);
$newIds = Set::fromArray([2, 3, 4])->difference($seen);

The first practical win is not “replace Laravel Collections everywhere.” It is replacing fragile array_filter/array_map/array_unique chains inside a single service, job, or domain object with a small, typed, testable pipeline.

Implementation Patterns

Boundary conversion, not wholesale replacement. Keep Eloquent, request validation, and Blade on Laravel’s Collection/arrays. Convert at the edge of the component that benefits from typed collections:

$orders = Vector::fromArray($eloquentOrders->all());

$summary = $orders
    ->filter(fn (Order $order): bool => $order->isPaid())
    ->map(fn (Order $order): int => $order->totalCents)
    ->reduce(fn (int $carry, int $total): int => $carry + $total, 0);

Return interfaces from domain code. Do not leak concrete classes from value objects and domain services unless you need a specific implementation:

use Psl\Collection\VectorInterface;

final readonly class OrderCollection
{
    /** @param VectorInterface<Order> $orders */
    public function __construct(private VectorInterface $orders) {}

    /** @return VectorInterface<Order> */
    public function paid(): VectorInterface
    {
        return $this->orders->filter(fn (Order $order): bool => $order->isPaid());
    }
}

This keeps call sites testable and makes the immutability contract explicit.

Use mutable variants as accumulators, immutable variants as results. For batch jobs or importers, mutation avoids repeated copying:

use Psl\Collection\MutableVector;
use Psl\Collection\Vector;

$buffer = MutableVector::fromArray([]);

foreach ($rows as $row) {
    if ($row->isValid()) {
        $buffer->push($row);
    }
}

$result = Vector::fromArray($buffer->toArray());

Treat the mutable collection as local working state and expose the immutable one outside the loop.

Use Map as an in-memory index. When you would otherwise call collect($users)->firstWhere('id', $id) repeatedly, build a map once:

$byId = Map::fromArray(collect($users)->keyBy('id')->all());

foreach ($events as $event) {
    $user = $byId->get($event->userId);
    if ($user === null) {
        continue;
    }
}

This is one of the clearest day-to-day wins over repeated Laravel collection scans.

Use Set for deduplication and membership tests. Set operations are clearer than nested array_unique/array_diff logic:

$alreadyImported = Set::fromArray($existingIds);
$toImport = Set::fromArray($incomingIds)->difference($alreadyImported);

Prefer the function API for hot paths. If you do not need an object, PSL’s Psl\Vec, Psl\Dict, and Psl\Iter namespaces operate on iterables and avoid collection object overhead. Use classes for stable APIs and value objects; use functions inside tight transformations, especially when working with generators or large payloads.

Integrate with Laravel at serialization points. API resources, cache payloads, and queue jobs should receive plain arrays:

public function toArray(Request $request): array
{
    return [
        'items' => $this->items->toArray(),
    ];
}

Going the other way is equally explicit:

$laravelCollection = collect($vector->toArray());

Test immutable collections as snapshots. Immutable collections make assertions stable and make accidental side effects obvious:

$this->assertSame(
    [2, 4, 6],
    Vector::fromArray([1, 2, 3])
        ->map(fn (int $value): int => $value * 2)
        ->toArray(),
);

Gotchas and Tips

  • Generics are docblock-level, not runtime-enforced. Vector<int> is a PHPStan/Psalm promise. PHP itself will not reject a Vector containing a string. Validate incoming payloads with Psl\Type or explicit checks before constructing the collection, and keep PHPStan or Psalm configured; without static analysis you lose the primary benefit.

  • Pin the PSL family consistently. The split package is generated from the monorepo. Mixing azjezz/psl, php-standard-library/psl, and individual split packages can produce duplicate classes or version conflicts. Choose one dependency strategy and run composer why php-standard-library/collection when in doubt.

  • Do not expect Laravel Collection parity. There is no pluck, where, load, lazy, chunk, relationship eager-loading, or query-builder integration. Convert Eloquent results to arrays at the boundary, then use PSL inside the service. If you need database-sized lazy processing, keep Laravel’s lazy collections or use PSL iterators around a generator you control.

  • Immutable operations copy. Chaining map, filter, and concat on an immutable Vector inside a large loop can create many intermediate collections. Use a MutableVector accumulator or Psl\Iter generators for large datasets. Benchmark before replacing a hot path; the readability win can be real while the allocation cost is also real.

  • Watch key semantics. Vector is list-like and should remain sequentially indexed. Map preserves keys and is intended for keyed data. After filtering or mapping, check whether you need to reindex (values()) before toArray() if the consumer expects a list. Know the difference between get() returning null for a missing key and at()/first() throwing when the position does not exist.

  • Set membership on objects uses object semantics. Deduplication of scalar IDs is straightforward. Deduplicating object instances is not the same as deduplicating by a domain key. If you need “same customer ID” semantics, build a Set or Map keyed by the scalar ID, not by the object.

  • Serialization needs testing. json_encode($vector) does not produce the same shape as json_encode($vector->toArray()). Always call toArray() before returning JSON, storing in cache, or dispatching in a queue payload. If you do serialize a collection object directly, test round-tripping through Laravel’s queue and cache drivers; do not assume it behaves like a plain array.

  • Mutable methods mutate in place. Do not write $vector = $vector->push($item) for mutable collections unless the signature actually returns a collection. Immutable methods return new instances, so ignoring the return value on an immutable collection is a silent no-op. Check the interface you are coding against, especially in shared services.

  • Debugging is easier after conversion. dump($vector->toArray()) and dd($map->toArray()) are usually more useful than dumping internal collection state. Collections are iterable, so iterator_to_array() also works when you need to inspect an iterable at a breakpoint.

  • Extension points are interfaces, not inheritance. Prefer implementing or consuming VectorInterface, MapInterface, and SetInterface over extending concrete classes. Concrete classes may be final or may change. For custom runtime validation, use Psl\Type; for custom lazy sources, feed a generator into PSL’s iterable-based functions.

  • Adoption risk is organisational as much as technical. This is a low-visibility split repository, so treat the monorepo as the source of truth, read its changelog before upgrades, and pilot in one non-critical service. Keep conversions at boundaries so you can fall back to Laravel collections without rewriting your domain layer.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib