laraditz/action
Define single-purpose Action classes for Laravel and Lumen to keep code DRY. Generate actions via artisan, pass data through constructor properties, and execute with handle() or a convenient static run() method. Includes a data() helper for all properties.
Installation:
composer require laraditz/action
Run the publish command (if needed for configuration):
php artisan vendor:publish --provider="Laraditz\Action\ActionServiceProvider"
Generate Your First Action:
php artisan make:action CreateUser
This creates a new class in app/Actions/CreateUser.php.
Define Dependencies and Logic:
namespace App\Actions;
use Laraditz\Action\Action;
use App\Models\User;
class CreateUser extends Action
{
public function __construct(
public string $name,
public string $email
) {}
public function handle(): void
{
User::create($this->data());
}
}
First Usage:
$action = new CreateUser(name: 'John Doe', email: 'john@example.com');
$action->handle();
vendor/laraditz/action/src/Action.php – Core logic and helpers.vendor/laraditz/action/src/Console/MakeActionCommand.php – How actions are scaffolded.vendor/laraditz/action/src/ActionServiceProvider.php – Registration and booting logic.Action Creation:
Use make:action to scaffold a new action with constructor properties and a handle() method. This enforces dependency injection and type safety.
Dependency Injection:
Pass required dependencies via the constructor. The data() helper method aggregates all constructor properties into an array for easy access.
$action = new ProcessOrder(
orderId: $orderId,
paymentMethod: $paymentMethod,
userId: auth()->id()
);
$action->handle();
Integration with Controllers: Use actions in controllers to encapsulate business logic, reducing controller bloat.
public function store(Request $request)
{
$action = new CreateUser(
name: $request->name,
email: $request->email
);
$action->handle();
return redirect()->route('users.index');
}
Reusable Actions: Create actions for common operations like validation, database operations, or API calls. For example:
// app/Actions/ValidateUserInput.php
class ValidateUserInput extends Action
{
public function __construct(
public array $rules,
public array $data
) {}
public function handle(): void
{
validator($this->data, $this->rules)->validate();
}
}
Chaining Actions: Combine multiple actions sequentially or conditionally.
$validate = new ValidateUserInput($rules, $request->all());
$validate->handle();
$createUser = new CreateUser(
name: $request->name,
email: $request->email
);
$createUser->handle();
Returning Data:
Use the handle() method to return data instead of void for actions that need to provide output.
public function handle(): array
{
return $this->data();
}
Then use it like this:
$result = (new GetUserProfile($userId))->handle();
Middleware Integration: Use actions in middleware to centralize logic like authentication or authorization checks.
public function handle($request, Closure $next)
{
$checkPermission = new CheckUserPermission(
userId: auth()->id(),
requiredPermission: 'edit_post'
);
$checkPermission->handle();
return $next($request);
}
Constructor Properties vs. Public Properties:
Avoid mixing constructor properties with additional public properties. The data() helper only aggregates constructor properties. If you need extra properties, use a method like getExtraData() or initialize them in the constructor.
// Bad: $this->extraData is not included in $this->data()
public $extraData;
// Good: Initialize in constructor
public function __construct(
public string $name,
public string $email,
public array $metadata = []
) {}
Type Safety:
The package doesn’t enforce return types for the handle() method. Explicitly declare return types (e.g., void, array, Model) to ensure consistency and IDE support.
public function handle(): User
{
return User::create($this->data());
}
Overriding data():
If you override the data() method, ensure it still returns an array of constructor properties for backward compatibility.
public function data(): array
{
return array_merge(parent::data(), ['custom_key' => 'value']);
}
Circular Dependencies: Avoid circular dependencies between actions. If Action A calls Action B, which in turn calls Action A, it will lead to infinite recursion or errors.
Lumen Compatibility: While the package supports Lumen, ensure you’re using the correct service provider registration. Lumen may require manual binding if not auto-discovered.
Check Constructor Properties:
Use var_dump($this->data()) inside handle() to verify the data being passed to the action.
Logging:
Add logging in the handle() method to trace execution flow.
\Log::info('Action executed with data:', $this->data());
Exception Handling: Wrap action calls in try-catch blocks to handle exceptions gracefully.
try {
$action->handle();
} catch (\Exception $e) {
\Log::error('Action failed:', ['error' => $e->getMessage()]);
abort(500, 'An error occurred.');
}
Artisan Command Issues:
If make:action isn’t working, ensure the service provider is registered in config/app.php under the providers array.
Custom Action Traits: Create reusable traits for common action behaviors, such as logging, validation, or transaction handling.
trait HandlesTransactions
{
public function handle(): void
{
\DB::beginTransaction();
try {
// Business logic
\DB::commit();
} catch (\Exception $e) {
\DB::rollBack();
throw $e;
}
}
}
Action Events: Dispatch events within actions to decouple logic and enable observability.
public function handle(): void
{
event(new UserCreated($this->data()));
User::create($this->data());
}
Dynamic Actions: Use reflection or dynamic class generation to create actions on the fly for highly dynamic workflows. However, this should be used sparingly due to complexity.
Testing Actions: Write unit tests for actions by mocking dependencies. Example:
public function test_create_user_action()
{
$action = new CreateUser(name: 'Test', email: 'test@example.com');
$this->assertNull($action->handle()); // Or assert the created user
}
Action Caching: Cache action instances if they’re stateless and called frequently (e.g., in middleware or filters).
$action = app()->makeWith(CreateUser::class, [
'name' => 'Cached User',
'email' => 'cached@example.com'
]);
How can I help you explore Laravel packages today?