PHPackages                             shafimsp/laravel-actions - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. shafimsp/laravel-actions

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

shafimsp/laravel-actions
========================

An action executor with middleware pipeline for Laravel applications.

v2.1.0(2mo ago)02MITPHPPHP ^8.2CI passing

Since Feb 25Pushed 2mo agoCompare

[ Source](https://github.com/shafimsp/laravel-actions)[ Packagist](https://packagist.org/packages/shafimsp/laravel-actions)[ RSS](/packages/shafimsp-laravel-actions/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (8)Versions (6)Used By (0)

Laravel Actions
===============

[](#laravel-actions)

A lightweight action executor with middleware pipeline for Laravel applications. Implements the CQRS pattern with convention-based handler resolution, return type validation, caching, and first-class testing support.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11.x or 12.x

Installation
------------

[](#installation)

```
composer require shafimsp/laravel-actions
```

The package auto-registers its service provider. Optionally publish the config:

```
php artisan vendor:publish --tag=actions-config
```

Quick Start
-----------

[](#quick-start)

### 1. Define an Action

[](#1-define-an-action)

Actions are simple classes that implement the `Action` interface and declare their return type via a docblock:

```
use ShafiMsp\Actions\Contracts\Action;

/** @implements Action */
final class FindUserById implements Action
{
    public function __construct(
        public readonly int $id,
    ) {}
}
```

### 2. Create a Handler

[](#2-create-a-handler)

By convention, the handler class is the action class name suffixed with `Handler` in the same namespace:

```
final class FindUserByIdHandler
{
    public function handle(FindUserById $action): User
    {
        return User::findOrFail($action->id);
    }
}
```

### 3. Execute the Action

[](#3-execute-the-action)

```
// Using the global helper
$user = execute(new FindUserById(1));

// Using the facade
use ShafiMsp\Actions\Facades\ActionExecutor;

$user = ActionExecutor::execute(new FindUserById(1));
```

Defining Actions
----------------

[](#defining-actions)

Declare the return type using the `@implements` docblock:

```
/** @implements Action */
final class GetGreeting implements Action {}

/** @implements Action */
final class FindOptionalUser implements Action {}

/** @implements Action */
final class SendEmail implements Action {}

/** @implements Action */
final class GetFlexibleResult implements Action {}
```

Supported return types: scalars (`string`, `int`, `float`, `bool`, `array`), objects, nullable types, union types, `void`, and `mixed`.

The executor validates the handler's return value against the declared type at runtime and throws a `RuntimeException` on mismatch.

Handler Resolution
------------------

[](#handler-resolution)

### Convention-Based (Default)

[](#convention-based-default)

Append `Handler` to the action class name in the same namespace:

ActionHandler`App\Actions\CreateUser``App\Actions\CreateUserHandler``App\Queries\FindUser``App\Queries\FindUserHandler`### Attribute-Based

[](#attribute-based)

Use the `#[HandledBy]` attribute to specify a custom handler:

```
use ShafiMsp\Actions\Attributes\HandledBy;

#[HandledBy(CustomHandler::class)]
final class CreateUser implements Action {}
```

Middleware
----------

[](#middleware)

Middleware wraps the action execution pipeline, enabling cross-cutting concerns like logging, authorization, or caching.

### Class-Based Middleware

[](#class-based-middleware)

```
use ShafiMsp\Actions\Contracts\Action;
use ShafiMsp\Actions\Contracts\Middleware;

final class LoggingMiddleware implements Middleware
{
    public function handle(Action $action, Closure $next): mixed
    {
        Log::info('Executing: ' . $action::class);
        $result = $next($action);
        Log::info('Completed: ' . $action::class);

        return $result;
    }
}
```

### Closure-Based Middleware

[](#closure-based-middleware)

```
use ShafiMsp\Actions\Facades\ActionExecutor;

ActionExecutor::pushMiddleware(function (Action $action, Closure $next) {
    // before
    $result = $next($action);
    // after
    return $result;
});
```

### Global Middleware

[](#global-middleware)

Register middleware for every action in `config/actions.php`:

```
return [
    'middleware' => [
        \ShafiMsp\Actions\Middleware\CacheMiddleware::class,
        \App\Actions\Middleware\LoggingMiddleware::class,
    ],
];
```

Middleware can short-circuit the pipeline by returning early without calling `$next()`, or modify the action before passing it along.

Caching
-------

[](#caching)

Cache action results automatically using the `#[Cacheable]` attribute:

```
use ShafiMsp\Actions\Attributes\Cacheable;

#[Cacheable(ttl: 3600)]
final class GetDashboardStats implements Action {}

#[Cacheable(ttl: 600, key: 'all_users')]
final class ListAllUsers implements Action {}
```

**Parameters:**

- `ttl` — Time to live in seconds (default: `3600`)
- `key` — Custom cache key (optional, auto-generated from action class and properties if omitted)

The `CacheMiddleware` is included in the default middleware stack. It uses cache tags when available and a distributed lock to prevent cache stampede.

Bootstrap Cache
---------------

[](#bootstrap-cache)

For production, generate a bootstrap cache to skip reflection on every request:

```
php artisan actions:cache
```

This discovers all actions, resolves their handlers and return types, and writes a cache file to `bootstrap/cache/actions.php`.

To clear:

```
php artisan actions:clear
```

Configuration
-------------

[](#configuration)

```
// config/actions.php

return [
    // Global middleware applied to every action execution
    'middleware' => [
        \ShafiMsp\Actions\Middleware\CacheMiddleware::class,
    ],

    // Bootstrap cache settings
    'cache' => [
        'enabled' => true,
        'directories' => [app_path()],
        'path' => null, // defaults to bootstrap/cache/actions.php
    ],
];
```

Testing
-------

[](#testing)

The package provides a `FakeExecutor` with a fluent assertion API.

### Faking the Executor

[](#faking-the-executor)

```
use ShafiMsp\Actions\Facades\ActionExecutor;

// Fake all actions (returns null by default)
ActionExecutor::fake();

// Fake with specific return values
ActionExecutor::fake([
    FindUserById::class => User::factory()->create(),
    GetGreeting::class => 'Hello!',
]);

// Fake with closures for dynamic values
ActionExecutor::fake([
    FindUserById::class => fn (FindUserById $action) => User::find($action->id),
]);
```

### Assertions

[](#assertions)

```
// Assert an action was executed
ActionExecutor::assertExecuted(FindUserById::class);

// Assert with a truth test
ActionExecutor::assertExecuted(
    FindUserById::class,
    fn (FindUserById $action) => $action->id === 1
);

// Assert executed exactly N times
ActionExecutor::assertExecutedTimes(FindUserById::class, 2);

// Assert an action was NOT executed
ActionExecutor::assertNotExecuted(DeleteUser::class);

// Assert nothing was executed at all
ActionExecutor::assertNothingExecuted();
```

### Full Test Example

[](#full-test-example)

```
public function test_show_returns_user(): void
{
    $user = User::factory()->create();

    ActionExecutor::fake([
        FindUserById::class => $user,
    ]);

    $response = $this->get("/users/{$user->id}");

    $response->assertOk();

    ActionExecutor::assertExecuted(
        FindUserById::class,
        fn (FindUserById $action) => $action->id === $user->id
    );
}
```

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance86

Actively maintained with recent releases

Popularity2

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity50

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 94.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

5

Last Release

74d ago

Major Versions

v1.2.0 → v2.0.02026-02-25

PHP version history (2 changes)v1.0.0PHP ^8.1

v2.0.0PHP ^8.2

### Community

Maintainers

![](https://www.gravatar.com/avatar/e1f9465447567538b9452968f4547310d123922b94842c3fa12feba9e198f9b5?d=identicon)[shafimsp](/maintainers/shafimsp)

---

Top Contributors

[![shafimsp](https://avatars.githubusercontent.com/u/13498024?v=4)](https://github.com/shafimsp "shafimsp (18 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (1 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/shafimsp-laravel-actions/health.svg)

```
[![Health](https://phpackages.com/badges/shafimsp-laravel-actions/health.svg)](https://phpackages.com/packages/shafimsp-laravel-actions)
```

###  Alternatives

[spatie/laravel-livewire-wizard

Build wizards using Livewire

4061.0M4](/packages/spatie-laravel-livewire-wizard)[illuminate/broadcasting

The Illuminate Broadcasting package.

7126.5M178](/packages/illuminate-broadcasting)[aedart/athenaeum

Athenaeum is a mono repository; a collection of various PHP packages

255.2k](/packages/aedart-athenaeum)[tonysm/importmap-laravel

Use ESM with importmap to manage modern JavaScript in Laravel without transpiling or bundling.

148399.8k1](/packages/tonysm-importmap-laravel)[bezhansalleh/filament-google-analytics

Google Analytics integration for FilamentPHP

205144.8k5](/packages/bezhansalleh-filament-google-analytics)[igorsgm/laravel-git-hooks

🪝• Efficiently manage Git hooks in Laravel projects. Enhance code quality, save time on reviews, and prevent bugs from entering your repository.

2931.7k1](/packages/igorsgm-laravel-git-hooks)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
