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

Php Grouper Laravel Package

leovie/php-grouper

leovie/php-grouper is a lightweight PHP utility for organizing arrays and collections into groups using custom rules. It offers a simple API for defining grouping logic and retrieving grouped results, helping keep data-processing code clean and readable.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Read the source before you install anything. The whole package is a handful of PHP files. Open src/ and confirm three things you cannot get from the README:

  • the exact entry-point class/namespace (the description calls it a "Grouper" with a "chainable API"; verify the real FQCN in src/ rather than trusting the docs);
  • the composer.json php constraint — the last tag is v2.0.1 (2022-05-25), so it was written against a PHP 8.0/8.1-era runtime;
  • whether there is a test suite at all, and what it covers for null keys and missing properties.

Ten minutes here saves you an afternoon of production debugging later.

2. Gate on compatibility, then pin.

composer why-not leovie/php-grouper 2.0.1   # against your actual app
composer require leovie/php-grouper:2.0.1   # exact pin, no caret

Do not use ^2.0. There has been no release in three years, no dependents, and a single maintainer. An exact pin means a future composer update on your wider stack fails loudly in CI instead of silently pulling an unknown commit.

3. Wrap it in one adapter before your first call site. This is the single most important step and costs you an hour:

namespace App\Support\Grouping;

use Illuminate\Support\Collection;

final class Grouper
{
    /**
     * @param  Collection<int, mixed>  $items
     * @return array<string, Collection<int, mixed>>
     */
    public static function by(Collection $items, callable $keyFn): array
    {
        // Implementation detail. Today: the package. Tomorrow: whatever.
        return $items->groupBy($keyFn)->all();
    }
}

Whether the body calls the package or Collection::groupBy() is now a one-file decision. Given the package's health signals, that adapter is the entire value of adopting it — it lets you spike, compare, and back out without touching application code.

4. First use case: something with zero blast radius. Not billing, not anything on the request path. Good first targets:

  • a nightly notification digest job that batches users;
  • a back-office CSV export that breaks orders down by region;
  • an admin dashboard widget grouping tickets by assignee + priority.

5. Spike the four edge cases immediately and write down observed behaviour:

// null values in the criteria
// a missing/undefined property on one object in the collection
// mixed types (int 1 vs string "1" vs 1.0)
// multi-criteria / nested grouping

Whatever the package actually does here is your contract. Encode it as tests in your suite, so a future replacement is validated against real expectations rather than the package's.


Implementation Patterns

The adapter is the pattern

Every call site goes through App\Support\Grouping\Grouper. Never scatter the package's FQCN across services. A leaf dependency with no support contract should appear in exactly one or two files in your codebase.

Single-key grouping: prefer groupBy in Laravel

If all you need is "bucket these models by status", Laravel already does it and you should not add a dependency:

$byStatus = Order::all()->groupBy('status');        // keyed collections

Reach for the package only when it earns its place: composite keys, chainable multi-level grouping, or reuse of grouping logic outside Eloquent collections (plain arrays of DTOs, stdClass rows from a query builder, decoded JSON).

Composite keys — the realistic reason to use it

The common genuine need is "break down by region and month" without each team inventing its own key-composition convention. Standardise the separator and the ordering in your adapter:

$grouped = Grouper::by($orders, fn (Order $o) => sprintf(
    '%s|%s',
    $o->region,
    $o->created_at->format('Y-m'),
));

Do the composition in your code, not the package's. Then the semantics (separator, timezone, date format, whether null becomes "null" or an empty segment) live where you can test and change them.

Nested / multi-level grouping

If the chainable API is real, compose it. Otherwise nest explicitly — it's clearer either way:

$byRegion = Grouper::by($orders, fn (Order $o) => $o->region);

$nested = collect($byRegion)->map(
    fn (Collection $orders) => Grouper::by($orders, fn (Order $o) => $o->status)
)->all();

Grouping Eloquent without N+1s

Group after the query, never as a substitute for with():

$tickets = Ticket::with('assignee')->where('open', true)->get();

$byAssignee = Grouper::by($tickets, fn (Ticket $t) => $t->assignee?->name ?? 'Unassigned');

If you group by a relation property, with() the relation or you'll lazy-load on every item inside the callback.

Large datasets: chunk, then group

Grouping is inherently in-memory; don't get() 500k rows to group them. Either aggregate in SQL (GROUP BY, which is almost always the right answer for reporting) or, if you genuinely need PHP-side grouping, use chunkById and merge:

$acc = [];
Order::query()->chunkById(1000, function ($orders) use (&$acc) {
    foreach (Grouper::by($orders, fn (Order $o) => $o->tenant_id) as $key => $group) {
        $acc[$key] = array_merge($acc[$key] ?? [], $group);
    }
});

Testing

Write your assertions against the adapter, not the package:

it('groups by a composed key and preserves insertion order', function () {
    $items = collect([/* ... */]);
    expect(Grouper::by($items, fn ($i) => "{$i->region}|{$i->month}"))
        ->toHaveKey('EU|2024-01')
        ->and(array_keys(Grouper::by($items, fn ($i) => $i->region)))
        ->toBe(['EU', 'US']); // ordering is part of the contract
});

Gotchas and Tips

This is unmaintained source code, not a supported library. Last release 2022-05-25, 0 stars, 0 dependents, one maintainer. Treat every bug you hit as an internal bug. Keep the BSD-3-Clause copyright notice intact if you vendor the source into app/Support.

PHP 8.3/8.4 will eventually bite. A 2022 codebase may use implicit nullable parameters or other constructs deprecated since. If your team tracks PHP majors, add the package to a composer outdated watch and re-check the adapter at each PHP upgrade. This is the highest-probability failure mode and it's entirely manageable because you wrapped it.

Key coercion is the classic silent bug. PHP turns numeric-string array keys into ints. If your grouping key is an ID or a "2024-01"-style string, "1" and 1 collapse into one bucket. If that matters, prefix keys ("id:1", "ym:2024-01") or compose a delimited string yourself.

Null and missing properties. Undocumented here and likely divergent from what you'd assume. Decide explicitly: does the criteria callback throw on a null property, or bucket it under ""? If the package calls a property name rather than a closure, test what happens when one object in the collection lacks it. In your adapter, normalise to a sentinel:

fn (Ticket $t) => $t->assignee?->name ?? 'Unassigned'

Never rely on the package's default null handling for anything user-facing.

Object identity vs. value equality. If two distinct objects with equal property values land in the same group, that's key-value grouping (usually what you want). But if the package groups by the object itself somewhere, you'll get one bucket per instance. Verify before shipping.

Cast and enum surprises. Eloquent casts mean the "raw" property isn't what your callback sees. $order->created_at is a Carbon, not a string — sprintf('%s', $order->created_at) won't give you the date format you expect. Enums need ->value. Cast explicitly inside the callback.

Output ordering. Insertion order preservation is claimed, but group key ordering is not specified. If a report renders groups in iteration order, that's a reshuffling support ticket waiting to happen — ksort() your output at the presentation layer.

Debugging. dd() on a nested grouped structure is painful. Dump the shape first:

$grouped = Grouper::by($items, $fn);
dump(array_map('count', $grouped));   // key => bucket size, instantly readable

And when behaviour surprises you, add a failing test before changing anything — you're defining the contract, not discovering it.

Memory. Grouping copies arrays of object references, so it's cheap per item, but the outer structure grows with cardinality. High-cardinality grouping (by user ID across millions of rows) is where a tiny general-purpose utility can surprise you. Benchmark at your real cardinality, or push the GROUP BY into SQL.

Extension point = the adapter. If you outgrow the package, delete the adapter body and implement ~50 lines over Collection::groupBy, or vendor the upstream source with its license header. Either way it's one file. That reversibility is the only strong argument for trying the package at all — the cost of being wrong is near zero, and the cost of not standardising your grouping semantics across teams is a recurring class of "these two dashboards disagree" bugs.

Verdict for day-to-day use: treat it as a reference design and a one-week spike, not a foundation. If your team's grouping needs are single-key, Collection::groupBy wins outright. If they're genuinely multi-key and multi-level, adopt the pattern — through the adapter — and keep the exit door open.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style