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

Knapsack Laravel Package

dusank/knapsack

Knapsack is a Laravel package for organizing applications into self-contained modules. It helps keep routes, views, config, migrations, and service providers grouped by module, making large Laravel codebases easier to structure, extend, and maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

Install with Composer; the package has no Laravel service provider and no config:

composer require dusank/knapsack

Where to look first:

  1. README.md — it is effectively the API reference; the method list is long but each entry is a one-liner.
  2. src/Collection.php — the whole library is essentially this one class. Skim the method signatures to see what is a pipeline stage (returns Collection) versus a terminal operation (returns a scalar/array).
  3. spec/ / test/ — the PHPSpec examples are the most reliable behavioural documentation, especially for key handling.

Minimal first use — transform an array lazily and materialise at the end:

use DusanKasan\Knapsack\Collection;

$total = Collection::from([1, 2, 3, 4, 5])
    ->map(fn (int $i) => $i * 10)
    ->filter(fn (int $i) => $i > 20)
    ->reduce(fn (int $carry, int $i) => $carry + $i, 0); // 120

Nothing above runs until reduce() is called. That is the core mental model: build the pipeline, then trigger it.

First real use case — stream a large file without loading it:

$handle = fopen('orders.ndjson', 'r');

$lines = (function () use ($handle) {
    while (($line = fgets($handle)) !== false) {
        yield json_decode($line, true);
    }
})();

$topCustomers = Collection::from($lines)
    ->filter(fn (array $o) => $o['status'] === 'paid')
    ->groupBy(fn (array $o) => $o['customer_id'])
    ->map(fn (Collection $orders) => $orders->reduce(fn ($c, $o) => $c + $o['total'], 0))
    ->toArray();

Implementation Patterns

Pipeline over a cursor, not a hydrated result set. The library shines when the source is a Traversable. Feed it PDOStatements, Cursor/LazyCollection from Eloquent, DirectoryIterator, or generators:

$paid = Collection::from(Order::query()->where('status', 'paid')->cursor())
    ->filter(fn (Order $o) => $o->total >= 100)
    ->indexBy(fn (Order $o) => $o->reference)
    ->map(fn (Order $o) => $o->total)
    ->toArray();

Memory stays flat because the query is never fully hydrated.

Compose small pure stages, reuse them. Because every stage returns a new Collection, pipelines are values you can pass around:

$onlyPaid = fn (Collection $c) => $c->filter(fn ($o) => $o['status'] === 'paid');
$withTotals = fn (Collection $c) => $c->map(fn ($o) => $o + ['net' => $o['total'] - $o['tax']]);

$report = $withTotals($onlyPaid(Collection::from($rows)))->toArray();

Index / group early, aggregate late. groupBy, indexBy, partition, and chunk are the workhorses for report-style code:

[$big, $small] = Collection::from($orders)
    ->partition(fn (Order $o) => $o->total > 500)
    ->toArray(); // [Collection, Collection]

Batch for side effects with chunk. Rather than a terminal each() over 500k rows, chunk into inserts:

Collection::from($rows)
    ->filter($validator)
    ->chunk(500)
    ->each(fn (Collection $batch) => DB::table('imports')->insert($batch->toArray()));

Early exit against unbounded streams. first, firstMatching, take, takeWhile stop pulling from the generator — this is where laziness pays for itself:

function naturals() { $i = 0; while (true) { yield $i++; } }

$firstFiveSquares = Collection::from(naturals())
    ->map(fn (int $i) => $i ** 2)
    ->take(5)
    ->values()
    ->toArray(); // [0, 1, 4, 9, 16]

Interop with Laravel's Collection. Knapsack is framework-agnostic, so bridge explicitly at the boundary rather than mixing idioms:

// Laravel LazyCollection -> Knapsack
$knapsack = Collection::from(User::cursor());

// Knapsack -> Laravel collection, for view/helpers
$laravel = collect($knapsack->toArray());

Keep Knapsack in the domain/service layer (where streams and iterators live) and Laravel collections in the presentation layer. One collection idiom per layer avoids "which map am I calling?" bugs.

Terminal operations you will use most: toArray, toJsonString/toString, count, each, first/last, reduce, sum/average/min/max, any/all/none/contains, get/extract. Anything not on that list is a stage and does nothing on its own.

Gotchas and Tips

  • Laziness means the stack trace lies to you. An exception thrown inside a map closure surfaces at the terminal call, not at the line that defined the pipeline. When debugging, insert ->take(5)->toArray() mid-pipeline — Collection is chainable and toArray() at an arbitrary point snapshots the intermediate state.
  • Pipelines re-execute. A Collection is a re-iterable recipe; iterating it twice runs every callback twice. Side effects inside map/filter (counters, writes, API calls) will fire again. Keep stages pure, or materialise once with toArray().
  • Beware passing a live Generator to from(). Unlike an array source, an instantiated generator cannot be rewound. If the collection is iterated twice you will get "Cannot rewind a generator that was already run" or silently truncated results. Pass a factory (fn () => $generator()) or a fresh iterator per use.
  • Keys are preserved by map/filter, matching array semantics. After filtering, toArray() can yield gaps, and json_encode will then emit an object instead of an array. Call ->values() or ->reindex() before handing data to an API boundary.
  • Some operations are inevitably eager. sort, reverse, count, toArray, and groupBy (it accumulates) must consume the whole source. Placing take(10) after sort() sorts everything; placing it before changes the result entirely. Order matters in a way it never does with an array.
  • count() on a lazy pipeline walks it. Calling count() then iterating doubles the work — and if the source is a generator, breaks it. Prefer counting the materialised array when you need both.
  • The package has not been released since 2017. Composer constraints are old; it works on modern PHP in practice because it is plain iterators and closures, but verify against your target version and watch for deprecations in your error log. Pin the version, and consider vendoring or forking if the pipeline is on a critical path — there are no dependents upstream to catch regressions for you.
  • No type stubs. You get docblocks, not generics. If you use PHPStan/Psalm, add @return Collection<int, Order> style annotations on your own wrapper functions, or the pipeline types degrade to mixed after the first map.
  • Performance is not free. Each stage allocates a generator and a Collection wrapper; for tight loops over small arrays, array_map/array_filter is several times faster. Reach for Knapsack when the win is memory (streaming) or early exit — not for microsecond-level transformations.
  • Extension point: the class is not designed for subclassing, and Collection::from() on a subclass loses the subtype. Wrap it instead — a small Report value object holding a Collection gives you named, testable domain pipelines.
  • Testing: assert only after a terminal operation. expect($collection)->toBe(...) on the intermediate object is meaningless. Standard pattern: build the pipeline in a private method, ->toArray() in the test, and assert on real arrays.
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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core