bekwoh/laravel-action
bekwoh/laravel-action is a lightweight package that brings a clean action pattern to Laravel. Encapsulate business logic in reusable, single-purpose action classes, keep controllers thin, and make workflows easier to test, compose, and maintain across your application. Simple to install and integrat...
This release (2.3.0) is a compatibility release: Laravel 13 support, PHPUnit 12, Pest 4. There are no new APIs to learn, so onboarding is still about adopting the action pattern rather than the package plumbing.
composer require cleaniquecoders/laravel-action then check the resolved illuminate/* constraint in composer.lock. The package now allows ^13.0, which means a careless composer update on a Laravel 11/12 app can still resolve fine — but if you are on Laravel 11, confirm the lower bound before assuming you are covered.tests/) rather than the README. Action packages tend to have thin docs and the tests are the real contract for how handle(), asJob(), and dependency injection are expected to behave.app/Actions/. That directory is the single place a reviewer should be able to open and understand every business operation that is not a controller, model, or job.Controller as a thin adapter. Controllers validate and delegate; the action owns the transaction and the side effects.
// app/Http/Controllers/RegisterController.php
public function store(RegisterRequest $request)
{
$user = RegisterUserAction::run($request->validated());
return redirect()->route('dashboard');
}
Compose small actions, don't build god-actions. Actions calling actions is the natural extension point, and it is the main reason to use the pattern at all. Keep each action to roughly one reason to change; if you find yourself injecting six services, split it.
Queue the same class you call synchronously. The highest-leverage use of an action package is that the identical code path runs inline in a request and async in a worker. Wrap anything slow (PDF generation, third-party syncs, mail fan-out) and switch it to queued dispatch without forking the logic. Test both paths — a class that works inline and explodes in a worker is the most common failure here.
Inject collaborators via the constructor, pass input via the method. Services, repositories, and clocks belong in the constructor (resolved by the container); request data, IDs, and DTOs belong in the method signature. Mixing the two makes actions untestable and unserializable.
Return typed results, not void. Have actions return a model, a DTO, or an explicit result object. This keeps call sites honest and gives you something to assert in tests. Actions returning bool are a smell — the caller ends up re-querying for state it should have received.
Integration tips for this release:
DB::transaction() inside the action, not in the controller, so the queued path gets the same guarantees.illuminate/*, not php), but you are outside what upstream tests. Check the package's composer.json require.php before upgrading, and treat PHP 8.3 as the practical floor for a smooth ride.phpunit.xml.dist. PHPUnit 12 dropped and renamed several configuration attributes and deprecated a batch of assertions. Do not wholesale-copy the updated dist file into an app on PHPUnit 10/11; it will fail to parse or silently ignore settings. Cherry-pick.serialize/unserialize) on the framework version you actually run before relying on them in production.handle().handle(). When three actions compose into one job, stack traces lose the originating request. A correlation ID passed into the action pays for itself the first time something fails at 2am.app/Actions become a junk drawer. Adopt a naming convention (VerbNounAction) and a rule that an action has exactly one public entry point. Actions with multiple public methods get called inconsistently and turn into service classes with worse names.prepare()-style methods), the job wrapper used for queued dispatch, and any config file. If you need retry/backoff/queue-name control, confirm whether the package exposes it or whether you must override the job class — this is where teams end up vendoring the package."cleaniquecoders/laravel-action": "^2.3" and commit the lock file. A compatibility release like this one is safe, but the next minor could tighten the PHP floor.How can I help you explore Laravel packages today?