codedoktor's avatar

Events and/or Actions

I am currently working on optimizing the structure of my code. One of the most interesting resources I came across was this well-known article on refactoring to actions. I also recently found this presentation by Adel Faizrakhmanov.

Now, I’m wondering: Should I use actions or events? Or should I combine both—using actions for synchronous tasks and events for asynchronous ones?

What do you think?

0 likes
9 replies
codedoktor's avatar

@jj15 I just watched it and it answered so many questions that were gathering in my head. I really try to be a better developer, but I think I am currently getting stuck in the "best practice" bubble. The things he is saying just make so much sense. Thanks for that!

jj15's avatar

@codedoktor You're welcome. I can also get sucked into the same bubble sometimes, always second-guessing myself and wondering if I'm doing it the "right" way. The truth is, there can be many "right" ways to do something. I think one of the hardest things to learn is the ability to determine which way is the most practical for a given project and use case.

Snapey's avatar

I use events but they can be confusing if you are new to the codebase or have not worked on the project for a while. Unless you know how events are processed it can be tricky to track down where something is being handled.

For many things this is an unnecessary abstraction

1 like
codedoktor's avatar

@Snapey I have worked with events in the past and I still find them to be too "abstract". Thinking about them I am like "Yeah this makes total sense". But when actually working with them I often find them to be hindering. Let's call it "obstruction through abstraction"

martinbean's avatar

Should I use actions or events?

@codedoktor You should use the appropriate solution for the appropriate problem. Actions and events do not solve the save problem.

  • Actions are classes that encapsulate some business logic, i.e. publishing a record.
  • Events are for saying something happened in your application, i.e. a record was published.

You should also only use patterns when it’s actually going to make your codebase easier to manage, and not just hastily re-factoring to a particular pattern because you happened to read about it that day.

So, for actions, a good reason to use them is if you have some logic you need to use in more than one spot. For example, if you did have a PublishRecord action, and users were able to publish records from more than one place in your application (i.e. their personal account, or an admin panel); or even in your application’s UI but also via an Artisan command. You could just then re-use your action class where needed:

public function someControllerAction(Record $record, PublishRecordAction $action)
{
    $action->handle($record);
}
class PublishRecordCommand extends Command
{
    protected $signature = 'record:publish {id}';

    public function handle(PublishRecordAction $action)
    {
        $record = Record::query()->findOrFail($this->argument('id'));

        $action->handle($record);

        $this->components->info('Record published.');

        return 0;
    }
}

Events are for communicating something happened. They’re not for encapsulating business logic like an action class is. So you could have a RecordPublished event that you dispatch in your PublishRecordAction class:

class PublishRecordAction
{
    public function handle(Record $record)
    {
        // Mark record as published...

        RecordPublished::dispatch($record);
    }
}

And then optionally have listeners that react to the record being published, i.e. send any notifications, update any statistics, etc.

1 like
codedoktor's avatar

@martinbean Thank you for taking the time to write such a comprehensive answer!

First of all, this is not about refactoring in this case. It's a fresh project in which I want to see what I can improve from earlier projects.

Regarding actions: I understand what you are saying, though I am just wondering if there is any way I can combine that with SRP => In this case removing all non-HTTP logic from the controllers. But that would mean I would have an action for basically everything (incl. actions that I might only use in one place). So what do you think is better?

  1. Having loads of actions eventhough some might only be used at one place
  2. Only use actions for reuseability reasons and leave the rest of the logic to the controller
martinbean's avatar

@codedoktor Again, try to only extract when it actually makes sense to.

For example, a lot of my controller actions are simple model retrievals or saves:

class ArticleController extends Controller
{
    public function store(StoreArticleRequest $request)
    {
        $article = Article::query()->create($request->validated());

        return redirect()->route('article.show', compact('article'));
    }

    public function show(Article $article)
    {
        return view('article.show', compact('article'));
    }
}

There’s absolutely no benefit of extracting those one-liners to “actions”. I’d only extract to an action if the logic was more complex, or if I needed the functionality of storing an article in multiple places in my application.

Please or to participate in this conversation.