Using Actions in Laravel: A Clean Approach to Business Logic
When building Laravel applications, one of the challenges developers often face is organizing business logic in a way that’s clean, reusable, and easy to maintain. Controllers can quickly become bloated, and models can end up doing more than they should. This is where actions come into play. Actions are single-purpose classes that encapsulate a specific piece of business logic, making your code more modular and easier to test.
In this post, I’ll walk you through an example of how to use actions in Laravel, explain the benefits of this approach.
What Are Actions?
Actions are essentially classes that handle a single responsibility. For example, instead of writing the logic to update a model directly in a controller or a service class, you can encapsulate that logic in an action class. This keeps your controllers thin and your codebase organized.
Here’s an example of an action class called UpdateModel:
class UpdateModel
{
public function execute(Model $model, array $attributes): Model
{
return DB::transaction(function () use ($model, $attributes) {
$model->updateOrFail(
Arr::only($attributes, $model->getFillable())
);
return $model;
});
}
}
In this example, the UpdateModel action is responsible for updating a given model with the provided attributes. It uses a database transaction to ensure data integrity and leverages updateOrFail to throw an exception if the update fails.
How to Use Actions in Your Application
Using actions in your application is straightforward. Let’s say you have a controller method that handles updating a user’s profile. Instead of writing the update logic directly in the controller, you can delegate that responsibility to the UpdateModel action.
Here’s how you might use the UpdateModel action in a controller:
$updatedUser = app(UpdateModel::class)->execute($user, $attributes);
In this example, the controller you are using is only responsible for handling the HTTP request and response. The actual business logic—updating the user—is delegated to the UpdateModel action. This separation of concerns makes your code easier to read, test, and maintain.
What Does getFillable() Do?
In Laravel, the getFillable() method is a built-in method on Eloquent models that returns an array of attributes that are mass-assignable. Mass assignment is a convenient way to update multiple attributes of a model at once, but it can also pose a security risk if not handled properly.
By defining a $fillable property on your model, you specify which attributes can be mass-assigned. For example:
class User extends Model
{
protected $fillable = [
'name',
'email',
'password',
];
}
In the UpdateModel action, Arr::only($attributes, $model->getFillable()) ensures that only the fillable attributes from the $attributes array are passed to the updateOrFail method. This prevents potential security issues by ensuring that only safe, whitelisted attributes are updated.
Benefits of Using Actions
1. Single Responsibility Principle (SRP)
Actions adhere to the Single Responsibility Principle by design. Each action handles one specific task, which makes your codebase more modular and easier to understand. For example, the UpdateModel action is only responsible for updating a model—nothing else.
2. Reusability
Actions can be reused across your application. For instance, if you need to update a model in multiple places (e.g., in a controller and a queue job), you can simply call the same action instead of duplicating code.
3. Testability
Because actions are isolated and self-contained, they’re easy to test. You can write unit tests for your actions without worrying about the complexities of the rest of your application. For example, testing the UpdateModel action would involve mocking the model and verifying that it updates correctly.
4. Improved Readability
By moving business logic out of controllers and into actions, your controllers become much cleaner and easier to read. This makes it easier for other developers (or your future self) to understand what’s happening in the code.
5. Transaction Handling
Actions make it easy to encapsulate database transactions. In the UpdateModel example, the entire update operation is wrapped in a transaction, ensuring data consistency. If you need to add more steps to the transaction later, you can do so without modifying the controller.
When to Use Actions
Actions are particularly useful for:
- Complex Business Logic: If a task involves multiple steps or requires interaction with several models, an action can help keep things organized.
- Repeated Logic: If you find yourself writing the same code in multiple places, it’s a good candidate for an action.
- Testing: If you want to make your code more testable, actions are a great way to isolate logic.
Final Thoughts
Using actions in Laravel is a powerful way to keep your codebase clean, maintainable, and testable. By encapsulating business logic in single-purpose classes, you can adhere to the Single Responsibility Principle, improve reusability, and make your controllers more focused on their primary role—handling HTTP requests and responses.
The UpdateModel action we discussed is just one example of how you can use actions in your application. As your application grows, you’ll likely find more opportunities to extract logic into actions, making your codebase more modular and easier to work with.
So, the next time you’re tempted to write business logic directly in a controller, consider creating an action instead. Your future self (and your team) will thank you!