creating-actions
by moonpixels
Laravel skeleton application template with Inertia React.
SKILL.md
name: creating-actions description: Create Laravel Action classes for business logic operations following domain-driven design. Use when creating Actions, implementing business logic, handling user operations, or when user mentions Action classes, domain operations, business rules, or service classes.
Create Laravel Actions
Create Action classes that encapsulate business logic in single-purpose, testable units. Actions receive typed DTOs, perform domain operations, and return results, keeping controllers thin and business rules centralized and reusable.
File Structure
app/Actions/{Domain}/{Name}Action.php
Examples:
app/Actions/Auth/RegisterUserAction.phpapp/Actions/User/UpdateUserPasswordAction.phpapp/Actions/Order/ProcessOrderAction.php
Core Conventions
1. Action Class Structure
<?php
declare(strict_types=1);
namespace App\Actions\{Domain};
use App\DTOs\{Domain}\{Name}Data;
use App\Models\{Model};
final readonly class {Name}Action
{
public function __construct(
private DependencyOne $dependencyOne,
) {}
public function handle({Name}Data $data): {ReturnType}
{
// Business logic here
return $result;
}
}
Key Requirements:
- Always include
declare(strict_types=1); - Use
finalclass modifier (always required) - Add
readonlymodifier when all class properties are readonly - Constructor injection for all dependencies
- Single
handle()method as the entry point - Accept DTOs for external data, primitives/models for internal logic
- Return domain objects, not HTTP responses
2. The readonly Keyword
// Use readonly - all properties are readonly via constructor
final readonly class RegisterUserAction
{
public function __construct(
private Localisation $localisation,
) {}
}
// No readonly - no constructor properties
final class DeleteUserAction
{
public function handle(User $user): bool
{
return (bool) $user->delete();
}
}
3. Naming Conventions
- Class name:
{Verb}{Noun}Action(e.g.,RegisterUserAction) - Method name: Always
handle() - Namespace:
App\Actions\{Domain}
4. Dependency Injection
public function __construct(
private UserRepository $users,
private EmailService $emailService,
) {}
- Inject services, repositories, or other dependencies
- Do NOT inject Request or other HTTP-specific classes
- Can inject other Actions for composition
Input & Output Patterns
Input: DTOs vs Primitives
Use DTOs when:
- Accepting data from external sources (HTTP requests, API calls)
- Data originates from Form Requests
- Multiple related fields need to be passed together
public function handle(RegisterData $data): User
{
return User::create([
'name' => $data->name,
'email' => $data->email,
'password' => Hash::make($data->password),
]);
}
Use Primitives/Models when:
- Handling internal business logic (triggered by jobs, events, services)
- Simple single-field operations
- Data is already validated/trusted
public function handle(User $user, string $password): bool
{
$user->password = Hash::make($password);
return $user->save();
}
Decision criteria:
- 3+ parameters? -> Use DTO
- From HTTP request? -> Use DTO
- Internal service call? -> Primitives are fine
Return Types
Return Model - When caller needs the updated instance:
public function handle(RegisterData $data): User
{
$user = User::create([...]);
return $user;
}
Return bool - For success/failure operations:
public function handle(User $user, string $password): bool
{
return $user->save();
}
Return void - For fire-and-forget operations:
public function handle(User $user): void
{
$user->delete();
}
Never return: HTTP Response objects, Request objects, or framework-specific types.
Anti-Patterns
Don't Do This
// Don't inject Request directly
public function __construct(private Request $request) {}
// Don't return Response objects
public function handle($data): JsonResponse
{
return response()->json(['success' => true]);
}
// Don't use mutable class properties
final class UpdateUserAction
{
private User $user; // No mutable state
}
// Don't skip type declarations
public function handle($data) // Must type hint
Do This Instead
// Accept DTOs from external sources
public function handle(UpdateUserData $data): User
// Accept primitives for internal logic
public function handle(User $user, string $password): bool
// Return domain objects
public function handle(RegisterData $data): User
// Use readonly when all properties are readonly
final readonly class UpdateUserAction
{
public function __construct(
private UserRepository $users
) {}
}
Integration with Controllers
Controllers should be thin and delegate to Actions:
public function store(
RegisterRequest $request,
RegisterUserAction $action
): RedirectResponse {
$user = $action->handle($request->toDTO());
auth('web')->login($user);
return redirect('dashboard');
}
Controller responsibilities: Validate input, call Action, handle HTTP response Action responsibilities: Execute business logic, interact with models/services, maintain data integrity
Quality Standards
- All Actions must pass PHPStan level 8
- 100% type coverage required
- Code formatted with Pint
- Refactored with Rector
- Covered by feature tests (see
writing-feature-testsskill)
References
- references/patterns.md - Advanced patterns (composition, transactions, exceptions)
- references/examples.md - Complete working examples
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
3ヶ月以内に更新がある
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon