Skip to content

Action Definition

lpachecob edited this page Mar 26, 2026 · 4 revisions

🧬 Action Definition: Declarative Use Cases

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.


🏗️ #[Action]

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 { ... }

🛠️ Complete Implementation Pattern

Here's the recommended pattern for BOUNDLY Actions with proper validation, DTOs, and error handling:

1. Create the Action Class

// 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
        ];
    }
}

2. Create the DTO

// 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,
        ];
    }
}

📦 Middleware Support

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()
        ];
    }
}

🔄 Override Default CRUD Behavior

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
        ];
    }
}

🧬 Why Actions over Controllers?

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

📋 Action Method Signature

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'];
}

🛤️ Route Pattern Guidelines

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

Recommended Patterns

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

Not Recommended (Won't Work)

  • posts/{id} - The framework doesn't support path parameters in resource
  • posts/{id}/like - Complex patterns are not supported

Handling IDs in Actions

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];
    }
}

Authentication in Actions

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];
}

🔗 Related Topics


Next Step: DTOs 📦

Clone this wiki locally