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

Group Action Bundle Laravel Package

idci/group-action-bundle

A Symfony bundle for adding grouped (bulk) actions to entity lists: select multiple rows and apply a shared action at once. Useful for batch delete, status changes, or custom operations in admin interfaces, with configurable action services and handlers.

View on GitHub
Deep Wiki
Context7

Getting Started

First, the framing. idci/group-action-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no config/group-action.php, no facade. composer require idci/group-action-bundle inside a Laravel app will install files and do nothing useful. So "getting started" splits into two tracks: evaluate the idea (worth it), and decide whether you ever run the code (only if you also operate a Symfony app).

Where to look first (10 minutes, no install):

  1. The README's usage example — it shows the intended contract: you select N entities in a list, POST the ids plus an action identifier to one endpoint, and the bundle resolves and runs the matching action.
  2. The action abstraction (an interface/abstract class you implement and register, typically as a DI-tagged service). Confirm the exact FQCN in the repo — the README is the only reliable source here, and at 0 stars / 0 dependents you should assume the docs are the spec.
  3. The controller/route the bundle exposes — this tells you the expected request shape (ids[] + action) and response format.
  4. Whether actions run synchronously or are expected to be dispatched to a message bus.

The Laravel translation (the actually useful first step). Reproduce the model in ~60 lines rather than adopting the dependency:

// app/Actions/Bulk/BulkAction.php
interface BulkAction {
    public function key(): string;          // 'delete', 'export', 'archive'
    public function authorize(Request $r): bool;
    public function handle(Collection $models, Request $r): BulkResult;
}

A registry keyed by key(), a single BulkActionController@__invoke, and per-model authorization via policies. That gives you 100% of the bundle's value with zero cross-framework friction.

First real use case: a paginated admin index with checkboxes and an "Apply to selected" dropdown — bulk delete, bulk status change, bulk CSV export. That is the exact scenario this bundle was built for, and it's the one where a naive foreach ($ids as $id) { Model::find($id)->delete(); } starts causing support tickets.

Implementation Patterns

Registry + single endpoint. Don't create a route per action. One endpoint, one form, action resolved by key:

Route::post('/admin/{resource}/bulk', BulkActionController::class)
    ->middleware(['auth', 'can:manage-admin']);

The controller validates action against the registry (never app($request->action) — that's arbitrary class instantiation from user input), resolves it, and passes ids.

Scope the id list through the authorized query, always. The single most important pattern:

$query = Post::query()->whereIn('id', $request->input('ids', []));
$query = $action->scope($query, $request->user()); // policy-aware
$models = $query->get();

Post::find($ids) is an IDOR waiting to happen: a user with access to the index can submit ids of records they cannot touch. Filter, don't fetch-then-check-if-you-remember.

Sync for small, queue for large. Cap the synchronous path (e.g. ≤ 200 ids) and push anything bigger to a job that carries ids, not models:

ExportPostsJob::dispatch($ids, $user->id);

Serializing Eloquent models into a queue payload is a classic way to get stale or unserializable state; ids re-resolve cleanly on the worker.

Return a per-id result, not a boolean. Partial failure is the normal case in bulk operations (3 of 40 rows were locked, 1 was already deleted). The bundle's grouped model implicitly expects this; make it explicit:

final class BulkResult {
    public function __construct(
        public int $succeeded = 0,
        public array $failed = [],   // id => reason
    ) {}
}

Then flash "37 updated, 3 skipped (locked)" instead of a lying "Done!".

Transaction boundary is a product decision, not a technical one. Wrap in DB::transaction() only when the operation is conceptually atomic (reassign ownership). For "archive 500 posts", commit per chunk — a failure at row 400 shouldn't roll back 399 successful changes the user already saw.

Chunk everything. ->chunkById(500, fn ($models) => ...) for memory, and for exports stream with cursor() or write to storage rather than building a giant CSV string in memory.

Reuse the same registry for non-obvious actions. Bulk export, bulk tag attach, bulk reorder, bulk "assign to me". If it takes a set of records and one intent, it belongs behind the same endpoint.

Test it as a feature test. post('/admin/posts/bulk', ['action' => 'delete', 'ids' => [$a->id, $b->id]]) plus one test per authorization edge — including "ids belonging to another tenant" and "unknown action key returns 422".

Gotchas and Tips

The framework mismatch is the headline gotcha. Symfony service tags, bundle registration in config/bundles.php, and the DI container have no Laravel equivalent. If you need the code (not just the pattern), you are committing to running a Symfony service beside your Laravel app and calling it over HTTP — which is a bigger architectural decision than a bulk-action helper deserves. Port the pattern; skip the dependency.

Zero dependents + recent release = treat the README as the contract. With 0 stars and 0 dependents there's no Stack Overflow corpus and no battle-testing. Read the source for the request/response shape rather than guessing, and pin the version if you do adopt it.

Validate the action key against an allowlist before it reaches the container. Both a security issue (Laravel's container will happily instantiate a class you name) and a UX one (unknown key should be a 422, not a 500).

Double submits. Bulk forms get double-clicked, especially on slow exports. Disable the submit button client-side and make the operation idempotent server-side (delete on already-deleted ids should be a no-op success, not a failure entry).

CSRF and long-lived forms. A big admin table left open past session expiry will fail the POST with a 419 that looks like a bug to users. Inertia/Livewire setups usually handle this; plain Blade forms need a refresh path.

Memory and timeouts masquerade as "the action is broken." 10k ids through get() + foreach + per-model save() will hit max_execution_time and look like a hang. chunkById plus ->update() set-based queries where possible.

Log the intent, not the loop. One log line per invocation — user, action key, id count, duration, failure count — beats 500 per-model lines. When someone asks "who archived these 200 records", you'll have the answer.

Extension points worth designing in from day one: the action registry (add actions without touching the controller), a scope()/authorize() hook per action (policy integration), a BulkResult value object (reporting), and a BulkActionCompleted event (audit log, notifications). These four are the difference between a reusable primitive and a controller that grows a new if every sprint.

Debugging checklist when a bulk action silently does nothing: (1) is ids actually an array and non-empty after validation? (2) did the authorized scope filter everything out — log $query->count() before and after? (3) is the queue worker running? (4) did you return early on a false authorization without telling the user? Most "broken" bulk actions are empty-scope or missing-worker problems, not logic bugs.

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.
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings