When you’re building a new Laravel application you frequently ask yourself questions like “Which controller do I need to create?”, “Will I need to create a job for this task?”, “where can I handle these requests?”, and one of the solutions to solve the problem with a place where you can write all your app logic — is to use Laravel action classes.
This article is dedicated to people who never used or want to learn how they can implement these action classes in the Laravel app, so let’s begin.
First of all, let’s create a new Laravel application and install the package that we will use in this tutorial to generate action classes in our app:
$ composer create-project laravel/laravel laravel-app
$ cd laravel-app/
$ composer require lorisleiva/laravel-actions
There is also a link to the official documentation of this package, so you can use this if you want to know more.
We need some model that we will use as an example — let’s type the following command in the terminal:
$ php artisan make:model Post -mf
I added the “-mf” flag at the end of the command because I want also to generate a migration and factory for this model. Open the migration file of the generated model, and add a few columns of table schema:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\User;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(User::class, 'user_id');
$table->string('title');
$tabke->text('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('posts');
}
};
Also don’t forget to configure the database connection in your application, and run the migration command:
$ php artisan migrate
Cool, now it’s time to edit the factory of the model, so let’s open the following file “database/factories/PostFactory.php” and paste the following code:
<?php
namespace Database\Factories;
use Illuminate\Database\Eloquent\Factories\Factory;
use App\Models\User;
/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\Post>
*/
class PostFactory extends Factory
{
/**
* Define the model's default state.
*
* @return array<string, mixed>
*/
public function definition(): array
{
return [
'user_id' => User::factory()->create(), // We will also generate a new user for each post
'title' => fake()->text(100),
'description' => fake()->text(200)
];
}
}
We just configured how random data will fill this column, and finally — open “database/seeders/DatabaseSeeder.php” and type the following code to generate 10 posts with random data:
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\Post;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*/
public function run(): void
{
Post::factory(10)->create();
}
}
Okay, and now let’s run migrations with seeder:
$ php artisan migrate:fresh --seed
Boom! It’s time to generate controller and actions classes that we will use to work with our posts in the database, so, use the following command to generate a posts controller:
$ php artisan make:controller PostController
We are going to build RESTful API in our app, it will be a great example, so, let’s generate the first action class before we are going to write some code inside “PostController”:
$ php artisan make:action CreatePost
Open this file in “app/Actions/CreatePost.php” and let’s write code to store a new post in the database:
<?php
namespace App\Actions;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Post;
use App\Models\User;
class CreatePost
{
use AsAction;
public function handle( $data, User $user )
{
Post::create([
'user_id' => $user->id,
'title' => $data['title'],
'description' => $data['description']
]);
}
}
Great, now let’s open the controller and let’s call this action on the store function:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Actions\CreatePost;
class PostController extends Controller
{
public function store(Request $request)
{
CreatePost::run( $request->all(), User::factory()->create() );
}
}
That was quite a simple example of using the Action class, what about a more complex example where we have many code in one function of some controller?
Let’s imagine that you have a likes system for your Post model, so, first let’s create a new model that will be named “Like”:
$ php artisan make:model Like -mf
Now let’s open the migration file of the Like model:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use App\Models\User;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('likes', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(User::class, 'user_id');
$table->morphs('likeable');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('likes');
}
};
Okay, and now let’s run fresh migration for our database:
$ php artisan migrate:fresh --seed
And also we need to make all fields in the Like model fillable:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Like extends Model
{
use HasFactory;
protected $guarded = [];
}
It’s time to use a model that we just created, now we are going to create a new action to like the selected post that already exists in the database:
$ php artisan make:action LikePost
Open this action class and paste the following code that will be used to create a new like for the selected post:
<?php
namespace App\Actions;
use Lorisleiva\Actions\Concerns\AsAction;
use App\Models\Post;
use App\Models\Like;
class LikePost
{
use AsAction;
public function handle(Post $post, $user)
{
$checkLike = Like::where('user_id', $user->id)
->where('likeable_type', 'App\Models\Post')
->where('likeable_id', $post->id)
->first();
if ( $checkLike === null ) {
return Like::create([
'user_id' => $user->id,
'likeable_type' => 'App\Models\Post',
'likeable_id' => $post->id
]);
}
return $checkLike->delete();
}
}
And finally, let’s use this action in the “PostController” to use this action:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Actions\CreatePost;
use App\Actions\LikePost;
use App\Models\Post;
use App\Models\User;
class PostController extends Controller
{
public function like(Post $post)
{
$user = User::first();
LikePost::run( $post, $user ); // This is just example, but as you can see by using actions you can reduce code in your controllers
}
public function store(Request $request)
{
CreatePost::run( $request->all() );
}
}
Boom🔥! That was a really simple example of how you can use action classes to reduce code in your controllers and build a more flexible structure in your app. There are all many other ways how you can organize your action classes in the app, what you saw in this article — is just one of the ways, also I recommend checking the official documentation of Laravel Actions.