What is an Action?
An Action represents a single discrete business capability in your domain. Rather than having a CourseController handle validation, database transactions, email notifications, and cache busting in a 200-line method, an Action handles the business logic directly.
Structure of a Clean Action
Actions typically live in app/Actions/{Domain}/ and expose an execute() or __invoke() method:
PHP
namespace App\Actions\Courses;
use App\Models\Course;
use App\Models\User;
use Illuminate\Support\Facades\DB;
class EnrollUserInCourse
{
public function execute(User $user, Course $course): void
{
DB::transaction(function () use ($user, $course) {
$user->enrolledCourses()->syncWithoutDetaching([$course->id]);
// Dispatch domain event or notification
});
}
}Thin Controllers with Actions
With actions, your controller becomes lightweight and purely responsible for HTTP parsing:
PHP
public function store(EnrollRequest $request, Course $course, EnrollUserInCourse $action)
{
$action->execute($request->user(), $course);
return back()->with('status', 'Enrolled successfully!');
}Testing Actions in Isolation
Testing actions with Pest is straightforward because they do not require HTTP requests or mocked controllers:
PHP
test('user can enroll in a course', function () {
$user = User::factory()->create();
$course = Course::factory()->create();
app(EnrollUserInCourse::class)->execute($user, $course);
expect($user->enrolledCourses)->toHaveCount(1);
});