Skip to content

cvtmal/symfony-action-pattern

Repository files navigation

Action Pattern in Symfony

🔄 Request Flow

  1. HTTP Request arrives at controller
  2. Controller creates/hydrates DTO from request
  3. Validator ensures DTO meets business rules
  4. Action executes business logic with validated DTO
  5. Controller transforms result to HTTP response
[HTTP Request] → [Controller] → [DTO + Validation] → [Action] → [Entity] → [Database]
                      ↓
              [HTTP Response]

🏗️ Architecture Philosophy

1. Thin Controllers

Controllers should be mere orchestrators, not business logic containers. Their responsibilities are limited to:

  • Receiving HTTP requests
  • Delegating to appropriate actions
  • Returning HTTP responses
#[Route('/new', name: 'app_todo_new', methods: ['GET', 'POST'])]
public function new(
    Request $request,
    CreateTodoAction $createTodoAction,
    ValidatorInterface $validator
): Response {
    // Controller only coordinates, doesn't implement business logic
    if ($request->isMethod('POST')) {
        // ... validate DTO
        $createTodoAction->execute($dto);  // Delegate to action
        // ... return response
    }
}

2. Single-Purpose Action Classes

Each action class has one job and does it well. This approach provides:

  • Single Responsibility: Each action handles exactly one business operation
  • Testability: Actions can be unit tested in isolation
  • Reusability: Actions can be called from controllers, console commands, message handlers, etc.
  • Readability: Intent is clear from the class name
class CreateTodoAction
{
    public function execute(CreateTodoDto $dto): Todo
    {
        // Single, focused responsibility
        $todo = new Todo();
        $todo->setTitle($dto->title);
        $todo->setDescription($dto->description);

        $this->entityManager->persist($todo);
        $this->entityManager->flush();

        return $todo;
    }
}

3. Data Transfer Objects (DTOs)

DTOs serve as validated, typed data containers that:

  • Validate input at the boundary of your application
  • Document expectations through property types and validation constraints
  • Decouple layers by not passing raw request data deep into your domain
class CreateTodoDto
{
    #[Assert\NotBlank(message: 'Title is required.')]
    #[Assert\Length(max: 255)]
    public string $title;

    public ?string $description = null;
}

4. Leveraging Symfony's Modern Features

  • #[MapRequestPayload]: Automatic request body to DTO hydration
  • Symfony Validator: Declarative validation rules on DTOs
  • Dependency Injection: Actions are services with autowired dependencies

🎯 Benefits of the Action Pattern

Maintainability

  • Separation of Concerns: Each class has a clear, single purpose
  • Easy to Locate Code: Need to change how todos are created? Look in CreateTodoAction
  • Predictable Structure: Consistent patterns across the application

Scalability

  • Team Collaboration: Clear boundaries prevent merge conflicts
  • Feature Addition: New features follow established patterns
  • Refactoring Safety: Small, focused classes are easier to refactor

Flexibility

  • Multiple Entry Points: Same action can be used from:
    • Web controllers
    • API endpoints
    • Console commands
    • Message/event handlers
    • Scheduled jobs

Project Structure

src/
├── Action/
│   └── Todo/
│       ├── CreateTodoAction.php    # Creates new todos
│       ├── UpdateTodoAction.php    # Updates existing todos
│       ├── DeleteTodoAction.php    # Removes todos
│       └── ToggleTodoAction.php    # Toggles completion status
├── Controller/
│   └── TodoController.php          # Thin HTTP layer
├── Dto/
│   ├── CreateTodoDto.php          # Input validation for creation
│   └── UpdateTodoDto.php          # Input validation for updates
├── Entity/
│   └── Todo.php                    # Domain model
└── Repository/
    └── TodoRepository.php          # Data access layer

Requirements

  • PHP 8.2+
  • Composer
  • SQLite (default) or MySQL/PostgreSQL

Installation

# Clone the repository
git clone <repository-url>
cd symftodo

# Install dependencies
composer install

# Create database and run migrations
php bin/console doctrine:database:create
php bin/console doctrine:migrations:migrate

# Start development server
symfony server:start

Contributing

This application serves as a reference implementation. Feel free to:

  • Fork and adapt the patterns to your needs
  • Suggest improvements via issues
  • Share your experiences with this architecture

MIT License - Feel free to use this as a starting point for your projects.

About

Opinionated Action Pattern for Symfony Applications

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published