-
Notifications
You must be signed in to change notification settings - Fork 0
Action Definition
In BOUNDLY, an Action is a single Use Case. You don't need routes/*.php files. Just add an attribute to your Action class and it becomes an API endpoint.
Required to register a class as an API endpoint.
| Property | Type | Default | Description |
|---|---|---|---|
resource |
string |
Required | The API path (e.g., 'users/register') |
method |
string |
'POST' |
HTTP method (GET, POST, PUT, DELETE, PATCH) |
middleware |
array |
[] |
Laravel middleware to apply |
#[Action(resource: 'users/register', method: 'POST')]
class RegisterUserAction { ... }Here's the recommended pattern for BOUNDLY Actions with proper validation, DTOs, and error handling:
// Application/Users/Actions/CreateUser.php
namespace Application\Users\Actions;
use Application\Users\DTOs\UserDTO;
use Infrastructure\FrameworkCore\Attributes\UseCase\Action;
use Infrastructure\FrameworkCore\Database\DynamicRepository;
use Illuminate\Http\Request;
#[Action(resource: 'users', method: 'POST')]
class CreateUser
{
public function __construct(
protected DynamicRepository $repository
) {}
public function execute(Request $request): array
{
// 1. Validate incoming data
$data = $request->validate([
'name' => 'required|string|max:150',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|min:8',
'phone' => 'nullable|string',
'address' => 'nullable|string',
]);
// 2. Create DTO to wrap validated data
$dto = UserDTO::fromRequest($data);
// 3. Persist via repository
$user = $this->repository->insert('users', $dto->toArray());
// 4. Return structured response
return [
'status' => 'success',
'message' => 'User created successfully',
'data' => $user
];
}
}// Application/Users/DTOs/UserDTO.php
namespace Application\Users\DTOs;
class UserDTO
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly ?string $phone = null,
public readonly ?string $address = null,
) {}
public static function fromRequest(array $data): self
{
return new self(
name: $data['name'],
email: $data['email'],
phone: $data['phone'] ?? null,
address: $data['address'] ?? null,
);
}
public function toArray(): array
{
return [
'name' => $this->name,
'email' => $this->email,
'phone' => $this->phone,
'address' => $this->address,
];
}
}You can add Laravel middleware directly to Actions:
#[Action(
resource: 'profile/private',
method: 'GET',
middleware: ['auth:sanctum', 'throttle:api']
)]
class GetPrivateProfileAction
{
public function execute(Request $request): array
{
return [
'status' => 'success',
'data' => $request->user()
];
}
}Actions take precedence over the auto-generated CRUD. This is useful when you need custom logic:
// Override POST /api/users with custom registration flow
#[Action(resource: 'users', method: 'POST')]
class RegisterUser
{
public function __construct(
protected DynamicRepository $repository
) {}
public function execute(Request $request): array
{
$validated = $request->validate([
'name' => 'required|string|max:150',
'email' => 'required|email|unique:users,email',
'password' => 'required|string|min:8|confirmed',
'accept_terms' => 'accepted',
]);
// Custom business logic: hash password, send welcome email, etc.
$validated['password'] = bcrypt($validated['password']);
$user = $this->repository->insert('users', $validated);
// Send welcome email, create default preferences, etc.
Mail::to($user['email'])->send(new WelcomeEmail($user));
return [
'status' => 'success',
'message' => 'Registration successful',
'data' => $user
];
}
}| Aspect | Traditional Controllers | BOUNDLY Actions |
|---|---|---|
| Location | App\Http\Controllers\ |
Application\{Entity}\Actions\ |
| Routing | Manual in routes/*.php
|
Declarative via #[Action]
|
| Responsibility | Can grow large | Single responsibility |
| Discovery | Manual registration | Auto-scanned |
| Testing | Mock full controller | Test single use case |
BOUNDLY Actions should implement an execute method that receives:
| Parameter | Type | Description |
|---|---|---|
Request $request |
Illuminate\Http\Request |
The HTTP request with validated data |
The method must return an array which BOUNDLY automatically converts to a JSON response.
public function execute(Request $request): array
{
// Your logic here
return ['key' => 'value'];
}BOUNDLY routes follow the pattern api/{resource}/{id?} where:
-
{resource}must be a simple alphanumeric name (letters and hyphens only) -
{id?}is optional and can be extracted from the route
| Pattern | Example | Description |
|---|---|---|
resource |
posts |
Resource for CRUD operations |
resource + body |
posts/like |
Resource with ID passed in request body |
resource + body |
users/follow |
Resource with ID passed in request body |
- ❌
posts/{id}- The framework doesn't support path parameters in resource - ❌
posts/{id}/like- Complex patterns are not supported
For actions that need a specific resource ID, pass it in the request body:
// Instead of #[Action(resource: 'posts/{id}/like', method: 'POST')]
#[Action(resource: 'posts/like', method: 'POST')]
class ToggleLikePost
{
public function execute(Request $request): array
{
// Get ID from request body
$postId = $request->input('post_id');
// Your logic here
return ['liked' => true];
}
}For authenticated endpoints, use the Auth facade directly (not $request->user()):
use Illuminate\Support\Facades\Auth;
public function execute(Request $request): array
{
$user = Auth::guard('sanctum')->user();
if (!$user) {
throw new ApiException('Unauthorized', ErrorCode::UNAUTHORIZED, 401);
}
// Your logic here
return ['user_id' => $user->id];
}- DTOs - How to use DTOs with Actions
- Behavioral-Traits - Rate limiting and other behaviors
- Entity-Definition - How entities work
Next Step: DTOs 📦