★ Refactoring to actions
Tags: php, laravel
Published: 2020-01-02 23:26:01

---
title: ★ Refactoring to actions
published: true
date: 2019-06-10 09:12:22 UTC
tags: php, laravel
canonical_url: https://freek.dev/1371-refactoring-to-actions
---

In our recent projects at [Spatie](https://spatie.be), we've started using a concept called "actions". It keeps our controllers and models skinny. It's a straightforward practice. In this blog post, I'd like to explain it to you.

## From logic in controllers and models... #

Consider you have a Laravel powered blog where you want to publish posts. When a post is published, the app should tweet out the title and link to it.

The controller that does that might look like this:

```php
class PostsController
{
    public function create()
    {
        // ...
    }

    public function store()
    {
        // ...
    }

    public function edit()
    {
        // ...
    }

    public function update()
    {
        // ...
    }

    public function delete()
    {
        // ...
    }

    public function publish(Post $post, TwitterApi $twitterApi)
    {
        $post->markAsPublished();

        $twitterApi->tweet($post->title . PHP_EOL . $post->url);

        flash()->success('Your post has been published!');

        return back();
    }
}
```

If you're wondering why this controller doesn't extend the default controller, head over to [this blog post on simplifying controllers](https://freek.dev/1324-simplifying-controllers).

To me, it's a bit dirty that a non-cruddy action lives in a crud controller. Let's follow [Adam's advice](https://github.com/adamwathan/laracon2017) and put the `publish` method in its own controller.

```php
class PublishPostController
{
    public function __invoke(Post $post, TwitterApi $twitter)
    {
        $post->markAsPublished();

        $twitter->tweet($post->title . PHP_EOL . $post->url);

        flash()->success('Your post has been published!');

        return back();
    }
}
```

That's already a bit nicer, but we can take it even further. Imagine you want to create an artisan command to publish blog posts. Right now, this isn't possible because the logic to do this is inside the controller.

To make logic callable from a command (or anywhere else in an app) that logic shouldn't be in a controller. Ideally, the only code that is placed in a controller is code that handles the HTTP layer.

You might be tempted to move all this code to a `publish` method on the `Post` model. For smallish projects that is fine. But imagine there are more kinds of actions on a post, like archiving or duplicating. All these actions will make your model big.

## ... to logic in actions! #

Instead of leaving this logic in the controller or putting it in a model, let's move it to a dedicated class. At Spatie, we call these classes "actions".

An action is a very simple class. It only has one public method: `execute`. You could name that method whatever you want.

```php
namespace App\Actions;

use App\Services\TwitterApi;

class PublishPostAction
{
    /** @var \App\Services\TwitterApi */
    private $twitter;

    public function __construct(TwitterApi $twitter)
    {
        $this->twitter = $twitter;
    }

    public function execute(Post $post)
    {
        $post->markAsPublished();

        $this->tweet($post->title . PHP_EOL . $post->url);
    }
    
    private function tweet(string $text)
    {
        $this->twitter->tweet($text);
    }
}
```

Notice that `markAsPublished` method being called on `$post`? Because our app now has a dedicated place for published a post, that logic could move to this `PublishPostAction`, making the `Post` model a bit lighter.

```php
// in PublishPostAction

public function execute(Post $post)
{
  $this->markAsPublished($post);

  $this->tweet($post->title . PHP_EOL . $post->url);
}

private function markAsPublished(Post $post)
{
  $post->published_at = now();

  $post->save();
}

private function tweet(string $text)
{
  $this->twitter->tweet($text);
}
```

In a controller, you can call the action like this:

```php
namespace App\Http\Controllers;

use App\Actions\PublishPostAction;

class PublishPostController
{
    public function __invoke(Post $post, PublishPostAction $publishPostAction)
    {
        $publishPostAction->execute($post);

        flash()->success('Hurray, your post has been published!');

        return back();
    }
}
```

We use method injection to resolve `PublishPostAction` so Laravel's container will automatically inject a `TwitterApi` instance into the `PublishPostAction` itself.

An artisan command could now make use of the action too.

```php
namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Actions\PublishPostAction;
use App\Models\Post;

class PublishPostCommand extends Command
{
    protected $signature = 'blog:publish-post {postId}';

    protected $description = 'Publish a post';

    public function handle(PublishPostAction $publishPostAction)
    {
        $post = Post::findOrFail($this->argument('postId'));
        
        $publishPostAction->execute($post);
        
        $this->comment('The post has been published!');
    }
}
```

Another benefit we get from extracting to actions is that the code because it isn't tied to the HTTP layer anymore, is now more testable.

```php
class PublishPostActionTest extends TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        Carbon::setTestNow(Carbon::createFromFormat('Y-m-d H:i:s', '2019-01-01 01:23:45'));

        TwitterApi::fake();
    }

    /** @test */
    public function it_can_publish_a_post()
    {
        $post = factory(Post::class)->state('unpublished')->create();

        (new PublishPostAction())->execute($post);

        $this->assertEquals('2019-01-01 01:23:45', $post->published_at->format('Y-m-d H:i:s'));

        TweetterApi::assertTweetSent();
    }
}
```

## Queueable actions #

Imagine you have an action that performs some work that takes some time. A simple solution for this would be to create [a queued job](https://laravel.com/docs/5.8/queues#creating-jobs) and dispatch that job from within the action.

Let's use a queue in the `PublishPostAction` to send out the tweet.

```php
// in PublishPostAction

public function execute(Post $post)
{
    $this->markAsPublished($post);

    $this->tweet($post->title . PHP_EOL . $post->url);
}

private function markAsPublished(Post $post)
{
    $post->published_at = now();

    $post->save();
}

private function tweet(string $text)
{
    dispatch(new SendTweetJob($text));
}
```

Now, if you want to send tweets from somewhere else in your application. Sure you could do it like this using a job:

```php
namespace App\Http\Controllers

class SendTweetController
{
    public function __invoke(SendTweetRequest $request)
    {
        dispatch(new TweetJob($request->text);
        
        flash()->success('The tweet has been sent');
        
        return back();
    }
}
```

That'll work perfectly. But wouldn't it be nice if we could use actions for everything, including asynchronous work?

Enter [our laravel-queueable-action package](https://github.com/spatie/laravel-queueable-action). This package allows you to queue actions easily. You can make an action queueable by applying the provided `QueueableAction` to it. That trait adds an `onQueue` method.

```php
use Spatie\QueueableAction\QueueableAction;

namespace App\Actions;

class SendTweetAction
{
    use QueueableAction;

    /** @var \App\Services\TwitterApi */
    private $twitter;

    public function __construct(TwitterApi $twitter)
    {
        $this->twitter = $twitter;
    }
    
    public function execute(string $text)
    {
        $this->twitter->tweet($text);
    }
}
```

Now we can call the action, and it will perform its work on a queue.

```php
class SendTweetController
{
    public function __invoke(SendTweetRequest $request, SendTweetAction $sendTweetAction)
    {
        $sendTweetAction->onQueue()->execute($request->text);
        
        flash()->success('The tweet will be sent very shortly!');
        
        return back();
    }
}
```

You can also specify the queue where the work should be performed by passing its name to `onQueue`.

```php
$sendTweetAction->onQueue('tweets')->execute($request->text);
```

If you want to know more about queueable actions, be sure to check our [this informative blog post](https://stitcher.io/blog/laravel-queueable-actions) by my colleague and package creator [Brent](https://twitter.com/brendt_gd).

## In closing #

Extracting logic to actions makes that action callable from multiple places in your app. It also makes the code easier to test. If actions become big, you can divide them into smaller actions.

At [Spatie](https://spatie.be), we've named this concept "action" and are using the `execute` method. You can call the concept and the method whatever you want. We didn't invent this practice. There are a lot of devs using it already. If you're coming from the DDD-world, you probably noticed that an action is just a command and its handler wrapped together.

Do you have any questions or remarks? Let me know in the comments below.