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

Actions Laravel Package

opscale-co/actions

Opscale Actions is a Laravel package for encapsulating business logic in reusable, testable action classes. Compose actions with dependency injection and validation to keep controllers and models thin, improve code organization, and make complex workflows easier to test and maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

opscale-co/actions gives you a single place to put business logic: a small, invokable class per operation instead of stuffing it into controllers, jobs, or model observers.

  1. Install and publish config:
    composer require opscale-co/actions
    php artisan vendor:publish --tag=actions-config
    
  2. Look first at the package's config file (namespace, whether actions are resolved from the container, queue defaults) and the make command signature — that tells you the intended class shape before you read anyone else's code.
  3. First use case: take one messy controller method (e.g. OrderController@store) and extract it verbatim into an action class. Call it from the controller; confirm behaviour is unchanged. This is the lowest-risk way to learn the resolver semantics and return-value conventions.

If you are on v4.1.0 or later, also skim the listener support (see below) — it changes how much duplication you need when the same operation is triggered both from HTTP and from domain events.

Implementation Patterns

One class, one verb. Actions are resolved through the container, so constructor injection of repositories, services, and config is the normal dependency path; method arguments are for runtime input. Keep actions free of Request objects — pass validated DTOs or scalars in.

Controller → action. Thin controllers: validate in a FormRequest, hand primitives to the action, and let the action own transactions and domain events. Returning a value from the action (a model, a result object) keeps the controller testable without touching the database.

Chaining/composition. Compose actions by injecting one action into another rather than making calls through the container inside handle(). This keeps the graph explicit and makes mocking trivial in tests.

Queued actions. Where the package offers queueing, prefer dispatching the action as a job over wrapping it in one — a wrapped job usually ends up re-implementing retry/backoff config the package already handles.

Event listeners (new in 4.1.0). #[AsListener] plus ListenerAdapter lets an action class double as an event listener. This is the right tool when the same operation fires from a controller and from a domain event:

#[AsListener(Event::class)]
final class SyncInventory implements ShouldQueue
{
    public function handle(Event $event): void { /* ... */ }
}

Use it to avoid parallel FooAction / FooListener pairs that inevitably drift. Keep listener-facing handle() signatures typed against the event, and delegate to a shared method if the action also has an HTTP entry point.

Integration tips. Register listeners through the attribute where possible so discovery is co-located with the code; keep the action's handle() free of event() calls that would recurse into the listener path.

Gotchas and Tips

  • Container resolution surprises. Constructor dependencies are resolved at call time; if you new Action() directly you bypass that and get confusing "too few arguments" errors. Always resolve via the container, or use the package's dispatch helper.
  • Attribute listeners need registration. If #[AsListener] classes are not being invoked, check that the listener-discovery path in the config covers your app/Actions namespace and that any cached event map is cleared (php artisan event:clear / optimize:clear) — cached maps are the usual culprit after upgrading to 4.1.0.
  • Signature mismatch in adapted listeners. ListenerAdapter bridges the action to the dispatcher; if your handle() type-hints a specific event class, mismatches surface as TypeError at dispatch time, not at boot. Write one assertion test per listener.
  • Queue + listener interaction. An action that is both queueable and used as a listener can silently defer side effects other code expects synchronously. Be explicit about ShouldQueue on the listener path.
  • Transactions and events. Wrap in DB::transaction() inside the action, not the controller, and use afterCommit for side effects — otherwise listener wiring multiplies the number of places that can fire events mid-transaction.
  • Testing. Assert on the action's return value and on dispatched events/jobs, not on internal method calls. One feature test per action plus one listener test is usually enough.
  • Extension points. The config file and the adapter/resolver bindings are the intended seams: override the adapter binding rather than subclassing actions when you need custom dispatch behaviour.
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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package