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

Reducible Laravel Package

arandu/reducible

arandu/reducible offers a simple, expressive way to reduce arrays, collections, and objects down to essentials—ideal for trimming payloads, building lean API resources, and keeping Laravel data output clean and consistent.

View on GitHub
Deep Wiki
Context7

Getting Started

Install it as a plain Composer dependency — there is no service provider to publish, so nothing to register in config/app.php:

composer require arandu/reducible

Open vendor/arandu/reducible/src/ first. The package is a single trait, so read the trait before anything else: you need the exact method names (reduce, addReducer, setReducers, or similar) and the reducer signature it expects. The rest of this assessment assumes a Redux-style contract — a reducer receives the current value plus optional context and returns the new value — but confirm that against the source, because with a 0-star package the README is thinner than the code.

The first use case is a service that other people need to tweak without editing it: totals, formatting, filtering, scoring. Instead of adding ten optional constructor arguments, you expose a single extension point.

namespace App\Support;

use Arandu\Reducible\Reducible;

class InvoiceTotals
{
    use Reducible;

    public function total(array $lines): int
    {
        $base = array_sum(array_column($lines, 'amount'));

        return $this->reduce($base, ['lines' => $lines]);
    }
}

Now a billing module can adjust the total without subclassing:

$totals = (new InvoiceTotals)->addReducer(
    fn (int $total, array $ctx) => $ctx['lines'] ? $total : 0
);

Implementation Patterns

Register reducers in a service provider, not at the call site. Treat the reducible class like a repository interface: bind one configured instance in a provider so your controllers never see the reducer list.

// AppServiceProvider::register()
$this->app->singleton(InvoiceTotals::class, function () {
    return (new InvoiceTotals)
        ->addReducer(new ApplyVatReducer)
        ->addReducer(new ApplyDiscountsReducer);
});

Accept class-strings, not just closures. Closures cannot be cached or serialized, and they make the pipeline invisible to php artisan about-style introspection. If the trait resolves callable|string through the container, prefer ApplyVatReducer::class so the reducer can take its own dependencies:

class ApplyVatReducer
{
    public function __construct(private TaxRate $rates) {}

    public function __invoke(int $total, array $ctx): int
    {
        return $total + $this->rates->vat($total);
    }
}

Drive the list from config when modules opt in. A config/billing.php array of reducer class-strings lets a package or a tenant toggle behaviour without touching PHP:

'reducers' => [
    App\Billing\Reducers\ApplyVatReducer::class,
    App\Billing\Reducers\ApplyDiscountsReducer::class,
],

then collect(config('billing.reducers'))->each(fn ($r) => $totals->addReducer($r));

Keep reducers pure and single-purpose. One reducer, one rule, returning a new value. This makes them trivially testable and reorderable:

it('applies vat', function () {
    expect((new ApplyVatReducer(new TaxRate(20)))->__invoke(100, []))->toBe(120);
});

Test the pipeline separately from the reducers. Assert order and composition on the reducible class, assert rules on each reducer. Two layers, two test files.

Use it at the edges of your domain, not in the middle. Good fits: price calculation, score/ranking, formatting pipelines, search-filter assembly, feature-flag-driven behaviour. Bad fits: anything where the order of operations is a business invariant nobody documents — that belongs in explicit code.

Gotchas and Tips

Order is almost certainly registration order. There is no priority argument in a package this small. If a discount must apply after VAT, that ordering lives in your service provider, so write a test that asserts it — otherwise a config reorder silently changes money.

Watch for mutation. PHP passes objects by handle. A reducer that does $invoice->total += 10 will bite you the moment two callers share the instance. Make reducers return new values, or make your DTOs readonly, and keep reducible classes out of long-lived singletons if state can leak.

mixed means no static analysis. Add generics-style docblocks on the trait usage and run PHPStan at a decent level; the trait's own signature will be loose and you want your call sites typed regardless.

Container resolution failures are opaque. If a reducer is a class-string that the container cannot build, you will get a binding exception far from the call site. Wrap resolution in a try/catch with the reducer name in the message, or pre-resolve all reducers in the provider so the failure happens at boot.

Do not capture closures into queued jobs. If a reducible object with closure reducers ends up on a queue, serialization will throw. Use invokable classes for anything that crosses a process boundary.

Debugging. Dump the registered list ($obj->reducers() / reflection on the trait's property) and, while tracking down a wrong total, temporarily replace reduce with a loop that logs each step's input and output. A five-line tap() around each reducer is usually faster than stepping through array_reduce.

Budget for maintenance. Zero stars and a last release in March 2024 means no community safety net. Pin the version in composer.json, and if this ends up load-bearing, copy the trait into app/Support/ — it is small enough that vendoring costs you an hour and removes the dependency risk entirely.

Extension points before you fork. Check whether the trait also pulls in Macroable or dispatches an event per reduction; if so, prefer those hooks over patching the package. If neither exists, the trait is thin enough that a subclass overriding reduce() gives you logging, caching, or short-circuiting without touching vendor code.

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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager