PHPackages                             sarfraznawaz2005/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. sarfraznawaz2005/actions

ActiveLibrary

sarfraznawaz2005/actions
========================

Laravel package as alternative to single action controllers with support for web and api in single class.

1.6.2(4y ago)661MITPHPPHP &gt;=7.0

Since Nov 19Pushed 4y ago1 watchersCompare

[ Source](https://github.com/sarfraznawaz2005/actions)[ Packagist](https://packagist.org/packages/sarfraznawaz2005/actions)[ RSS](/packages/sarfraznawaz2005-actions/feed)WikiDiscussions master Synced today

READMEChangelog (6)Dependencies (1)Versions (26)Used By (0)

[![Software License](https://camo.githubusercontent.com/55c0218c8f8009f06ad4ddae837ddd05301481fcf0dff8e0ed9dadda8780713e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6c6963656e73652d4d49542d627269676874677265656e2e7376673f7374796c653d666c61742d737175617265)](license.md)[![Latest Version on Packagist](https://camo.githubusercontent.com/f0c6ed1df30b53be568c109ab7457a8eb4f43456fa07951a22f3c7968cf67dc2/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7361726672617a6e6177617a323030352f616374696f6e732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/sarfraznawaz2005/actions)[![Total Downloads](https://camo.githubusercontent.com/3345e66677ce1e229462e207d312ad9fa3e1b00a74970660f3da699eb41d84c3/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7361726672617a6e6177617a323030352f616374696f6e732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/sarfraznawaz2005/actions)

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

[](#laravel-actions)

Laravel package as an alternative to [single action controllers](https://laravel.com/docs/master/controllers#single-action-controllers) with support for web/html and api in single class. You can use *single* class called *Action* to send appropriate web or api response *automatically*. It also provides easy way to validate request data.

Under the hood, action classes are normal Laravel controllers but with single public `__invoke` method. This means you can do anything that you do with controllers normally like calling `$this->middleware('foo')` or anything else.

Table of Contents
-----------------

[](#table-of-contents)

- [Why](#why)
- [Requirements](#requirements)
- [Installation](#installation)
- [Example Action Class](#example-action-class)
- [Usage](#usage)
- [Send Web or API Response Automatically](#send-web-or-api-response-automatically)
- [Validation](#validation)
- [Utility Methods and Properties](#utility-methods-and-properties)
- [Creating Actions](#creating-actions)
- [Registering Routes](#registering-routes)
- [Bonus: Creating Plain Classes](#bonus-creating-plain-classes)

Why
---

[](#why)

- Helps follow single responsibility principle (SRP)
- Helps keep controllers and models skinny
- Small dedicated class makes the code easier to test
- Helps avoid code duplication eg different classes for web and api
- Action classes can be callable from multiple places in your app
- Small dedicated classes really pay off in complex apps
- Expressive routes registration like `Route::get('/', HomeAction::class)`
- Allows decorator pattern

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

[](#requirements)

- PHP &gt;= 7
- Laravel 5, 6

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

[](#installation)

Install via composer

```
composer require sarfraznawaz2005/actions

```

That's it.

---

Example Action Class
--------------------

[](#example-action-class)

```
class PublishPostAction extends Action
{
    /**
     * Define any validation rules.
     */
    protected $rules = [];

    /**
     * Perform the action.
     *
     * @return mixed
     */
    public function __invoke()
    {
        // code
    }
}
```

In `__invoke()` method, you write actual logic of the action. Actions are invokable classes that use `__invoke` magic function turning them into a `Callable` which allows them to be called as function.

Usage
-----

[](#usage)

**As Controller Actions**

Primary usage of action classes is mapping them to routes so they are called automatically when visiting those routes:

```
// routes/web.php

Route::get('post', '\App\Http\Actions\PublishPostAction');

// or

Route::get('post', '\\' . PublishPostAction::class);
```

> *Note that the initial `\` here is important to ensure the namespace does not become `\App\Http\Controller\App\Http\Actions\PublishPostAction`*

**As Callable Classes**

```
$action = new PublishPostAction();
$action();
```

Send Web or API Response Automatically
--------------------------------------

[](#send-web-or-api-response-automatically)

If you need to serve both web and api responses from same/single action class, you need to define `html()` and `json()` method in your action class:

```
class TodosListAction extends Action
{
    protected $todos;

    public function __invoke(Todo $todos)
    {
        $this->todos = $todos->all();
    }

    protected function html()
    {
        return view('index')->with('todos', $this->todos);
    }

    protected function json()
    {
        return TodosResource::collection($this->todos);
    }
}
```

With these two methods present, the package will *automatically* send appropriate response. Browsers will receive output from `html()` method and other devices will receive output from `json()` method.

Under the hood, we check if `Accept: application/json` header is present in request and if so it sends output from your `json()` method otherwise from `html()` method.

You can change this api/json detection mechanism by implementing `isApi()` method, it must return `boolean` value:

```
class TodosListAction extends Action
{
    protected $todos;

    public function __invoke(Todo $todos)
    {
        $this->todos = $todos->all();
    }

    protected function html()
    {
        return view('index')->with('todos', $this->todos);
    }

    protected function json()
    {
        return TodosResource::collection($this->todos);
    }

    public function isApi()
    {
        return request()->wantsJson() && !request()->acceptsHtml();
    }

}
```

**Using Action Classes for API Requests Only**

Simply return `true` from `isApi` method and use `json` method.

**Using Action Classes for Web/Browser Requests Only**

This is default behaviour, you can simply return your HTML/blade views from within `__invoke` or `html` method if you use it.

Validation
----------

[](#validation)

You can perform input validation for your `store` and `update` methods, simply use `protected $rules = []` property in your action class:

```
class TodoStoreAction extends Action
{
    protected $rules = [
        'title' => 'required|min:5'
    ];

    public function __invoke(Todo $todo)
    {
        $todo->fill(request()->all());

        return $todo->save();
    }
}
```

In this case, validation will be performed before `__invoke` method is called and if it fails, you will be automatically redirected back to previous form page with `$errors` filled with validation errors.

> **Tip:** Because validation is performed before `__invoke` method is called, using `request()->all()` will always give you valid data in `__invoke` method which is why it's used in above example.

**Custom Validation Messages**

To implement custom validation error messages for your rules, simply use `protected $messages = []` property.

**Ignoring/Filtering Request Data**

If you want to remove some request data before it is validated/persisted, you can use the `protected $ignored = ['id'];`. In this case, `id` will be removed from the request eg in other words it will be as if it was not posted in the request.

Utility Methods and Properties
------------------------------

[](#utility-methods-and-properties)

Consider following action which is supposed to save todo/task into database and send appropriate response to web and api:

```
class TodoStoreAction extends Action
{
    protected $rules = [
        'title' => 'required|min:2'
    ];

    public function __invoke(Todo $todo)
    {
        return $this->create($todo);
    }

    protected function html($result)
    {
        if (!$result) {
            return back()->withInput()->withErrors($this->errors);
        }

        session()->flash('success', self::MESSAGE_CREATE);
        return back();
    }

    protected function json($result)
    {
        if (!$result) {
            return response()->json(['result' => false], Response::HTTP_INTERNAL_SERVER_ERROR);
        }

        return response()->json(['result' => true], Response::HTTP_CREATED);
    }
}
```

There are few things to notice above that package provides out of the box:

- Inside `__invoke` method, we used `$this->create` method as shorthand/quick way to create a new todo record. Similarly, `$this->update` and `$this->delete` methods can also be used. They all return `boolean` value. They all also accept optional callback:

```
return $this->create($todo, function ($result) {
    if ($result) {
        flash(self::MESSAGE_CREATE, 'success');
    } else {
        flash(self::MESSAGE_FAIL, 'danger');
    }
});
```

Using these utility methods is not required though.

- If you return something from `__invoke` method, it can be read later from `html` and `json` methods as first parameter. In this case, boolean result of todo creation (`return $this->create($todo)`) was used in both `html` and `json` methods via `$result` variable whos name can be anything.
- Any validation errors are saved in `$this->errors` variable which can be used as needed.
- In `html()` method, we have used `self::MESSAGE_CREATE` which comes from parent action class. Similar, `self::MESSAGE_UPDATE`, `self::MESSAGE_DELETE` and `self::MESSAGE_FAIL` can also be used.

> **Tip:** You can choose to not use any utility methods/properties/validations offered by this package which is completely fine. Remember, action classes are normal Laravel controllers you can use however you like.

**Transforming Request Data**

If you want to transform request data *before* validation is performed and *before* `__invoke()` method is called, you can define `transform` method in your action class which must return an array:

```
public function transform(Request $request): array
{
    return [
        'description' => trim(strip_tags($request->description)),
        'user_id' => auth()->user->id ?? 0,
    ];
}
```

The transform method can be used to both *modify* existing request variables as well as *adding* new variables to request data. In above example, we modify `description` to trim any whitespace and remove any html tags. We also add `user_id` to request data which wasn't in it before.

Behind the scene, we simply merge whatever is returned from this method into original Request data.

Creating Actions
----------------

[](#creating-actions)

[![Screen](https://github.com/sarfraznawaz2005/actions/raw/master/screen.png?raw=true)](https://github.com/sarfraznawaz2005/actions/blob/master/screen.png?raw=true)

- Create an action

```
php artisan make:action ShowPost
```

> `ShowPost` action will be created

- Create actions for all resource actions (`index`, `show`, `create`, `store`, `edit`, `update`, `destroy`)

```
php artisan make:action Post --resource
```

> `IndexPost`, `ShowPost`, `CreatePost`, `StorePost`, `EditPost`, `UpdatePost`, `DestroyPost` actions will be created

- Create actions for all API actions (`create`, `edit` excluded)

```
php artisan make:action Post --api
```

> `IndexPost`, `ShowPost`, `StorePost`, `UpdatePost`, `DestroyPost` actions will be created

- Create actions by the specified actions

```
php artisan make:action Post --actions=show,destroy,approve
```

> `ShowPost`, `DestroyPost`, `ApprovePost` actions will be created

- Exclude specified actions

```
php artisan make:action Post --resource --except=index,show,edit
```

> `CreatePost`, `StorePost`, `UpdatePost`, `DestroyPost` actions will be created

- Specify namespace for actions creating (relative path)

```
php artisan make:action Post --resource --namespace=Post
```

> `IndexPost`, `ShowPost`, `CreatePost`, `StorePost`, `EditPost`, `UpdatePost`, `DestroyPost` actions will be created under `App\Http\Actions\Post` namespace in `app/Http/Actions/Post` directory

- Specify namespace for actions creating (absolute path)

```
php artisan make:action ActivateUser --namespace=\\App\\Foo\\Bar
```

> `ActivateUser` action will be created under `App\Foo\Bar` namespace in `app/Foo/Bar` directory

- Force create

```
php artisan make:action EditPost --force
```

> If `EditPost` action already exists, it will be overwritten by the new one

Registering Routes
------------------

[](#registering-routes)

Here are several ways to register actions in routes:

#### In separate `actions.php` route file

[](#in-separate-actionsphp-route-file)

- Create `routes/actions.php` file (you can choose any name, it's just an example)
- Define the "action" route group in `app/Providers/RouteServiceProvider.php`

> ##### With namespace auto prefixing
>
> [](#with-namespace-auto-prefixing)

```
// app/Providers/RouteServiceProvider.php

protected function mapActionRoutes()
{
    Route::middleware('web')
         ->namespace('App\Http\Actions')
         ->group(base_path('routes/actions.php'));
}
```

```
// app/Providers/RouteServiceProvider.php

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();

    $this->mapActionRoutes();

    //
}
```

```
// routes/actions.php

Route::get('/post/{post}', 'ShowPost');
```

> ##### Without namespace auto prefixing
>
> [](#without-namespace-auto-prefixing)

```
// app/Providers/RouteServiceProvider.php

protected function mapActionRoutes()
{
    Route::middleware('web')
         ->group(base_path('routes/actions.php'));
}
```

```
// app/Providers/RouteServiceProvider.php

public function map()
{
    $this->mapApiRoutes();

    $this->mapWebRoutes();

    $this->mapActionRoutes();

    //
}
```

```
// routes/actions.php

use App\Actions\ShowPost;

Route::get('/post/{post}', ShowPost::class); // pretty sweet, isn't it? 😍
```

#### In `web.php` route file

[](#in-webphp-route-file)

- Change the namespace for "web" group in `RouteServiceProvider.php`

```
// app/Providers/RouteServiceProvider.php

protected function mapWebRoutes()
{
    Route::middleware('web')
         ->namespace('App\Http') // pay attention here
         ->group(base_path('routes/web.php'));
}
```

- Put actions and controllers in different route groups in `routes/web.php` file and prepend an appropriate namespace for each of them

```
// routes/web.php

Route::group(['namespace' => 'Actions'], function () {
    Route::get('/posts/{post}', 'ShowPost');
    Route::delete('/posts/{post}', 'DestroyPost');
});

Route::group(['namespace' => 'Controllers'], function () {
    Route::get('/users', 'UserController@index');
    Route::get('/users/{user}', 'UserController@show');
});
```

Bonus: Creating Plain Classes
-----------------------------

[](#bonus-creating-plain-classes)

The package also provides `make:class` console command to create plain classes:

```
php artisan make:class FooBar
```

`FooBar` class will be created under `app/Actions` folder:

```
namespace App\Actions;

class FooBar
{
    /**
     * Perform the action.
     *
     * @return mixed
     */
    public function execute()
    {
        //
    }
}
```

Note that these are plain old PHP classes you can use for any purpose. *Ideally*, they should not be dependent on Laravel framework or any other framework and should have single public method as api such as `execute` and any more private/protected methods needed for that class to work. This will allow you to use them across different projects and frameworks. You can also think of them as service classes.

Credits
-------

[](#credits)

- [Sarfraz Ahmed](https://github.com/sarfraznawaz2005)
- [All Contributors](https://github.com/sarfraznawaz2005/actions/graphs/contributors)

License
-------

[](#license)

Please see the [license file](license.md) for more information.

###  Health Score

29

—

LowBetter than 60% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity14

Limited adoption so far

Community9

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 52.6% 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 ~30 days

Recently: every ~19 days

Total

25

Last Release

1643d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/3f9bbcf3a6cda5f8bdf9afe10c20f5c6f563fc1a4d628a9af25c5fdec3f9c216?d=identicon)[sarfraznawaz2005](/maintainers/sarfraznawaz2005)

---

Top Contributors

[![sarfraznawaz2005](https://avatars.githubusercontent.com/u/201788?v=4)](https://github.com/sarfraznawaz2005 "sarfraznawaz2005 (20 commits)")[![sarfrazonsupport](https://avatars.githubusercontent.com/u/142378572?v=4)](https://github.com/sarfrazonsupport "sarfrazonsupport (18 commits)")

---

Tags

actionscleancodecontrollerlaravelsinglesrplaravelclasscontrollersingleaction

### Embed Badge

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

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

###  Alternatives

[lorisleiva/laravel-actions

Laravel components that take care of one specific task

2.8k7.5M115](/packages/lorisleiva-laravel-actions)[hivokas/laravel-handlers

Request handlers for Laravel

1296.2k](/packages/hivokas-laravel-handlers)[phaza/single-table-inheritance

Single Table Inheritance Trait

1515.8k](/packages/phaza-single-table-inheritance)

PHPackages © 2026

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