Skip to content
Agentmart
Submit a tool

action-pattern

Creating and using action classes to encapsulate business logic.

Skill by ceilidhboy

IndexedMIT
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/ceilidhboy-action-pattern

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Creating and using action classes to encapsulate business logic. Actions replace traditional Service classes with reusable, composable, single-responsibility classes that follow a consistent `execute()` convention.

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/action-pattern

What's inside

Skill name
action-pattern
Description
Creating and using action classes to encapsulate business logic. Actions replace traditional Service classes with reusable, composable, single-responsibility classes that follow a consistent `execute()` convention.
License
MIT
Instructions
287 lines in SKILL.md · view on GitHub

Files (1)

  • SKILL.md

SKILL.md (first lines)

# Action Pattern

## Overview

The Action pattern encapsulates business logic into reusable, composable, single-responsibility classes. Actions replace traditional "Service" classes and provide a clean, testable way to organize application behavior.

## Core Principles

### 1. Single Responsibility Principle (SRP)

Each action should have **one reason to change**. If a class is doing multiple things, it should be split into multiple actions, not multiple methods within the same class.

**Key Question**: Does this action have more than one responsibility? If yes, split it into separate actions.

**Note**: This same principle applies to **tasks** in the task list. See [single-task-responsibility](../single-task-responsibility/SKILL.md) skill for guidance on recognizing when task implementation violates SRP and requires stopping to consult the user.

### 2. The `execute()` Method

Every action **must** have a public `execute()` method. This is the convention and must not be flexible.

- **Method signature**: Parameters and return type depend entirely on the action's functionality
- **Naming**: Always `execute()` — never `handle()`, `__invoke()`, or other alternatives
- **Statelessness**: The method should be callable multiple times with different parameters without side effects

**One exception — vendor interfaces.** A class implementing a framework interface whose contract names the method keeps that name: the interface is the authority, not this convention. Laravel Fortify is the case in practice — it publishes `app/Actions/Fortify/CreateNewUser.php` and `ResetUserPassword.php` and expects `create()` and `reset()` through `CreatesNewUsers` and `ResetsUserPasswords`.

The namespace carries the distinction, so no judgement is needed at review time:

- `app/Actions/<Domain>/` — our actions: `final readonly class`, one public `execute()`.
- `app/Actions/<Vendor>/` — vendor-published interface implementations. Their method names are imposed by the interface they implement.

Two consequences worth knowing:

- **Do not relocate vendor-published actions out of their vendor directory.** Fortify's installer writes its stubs to `app_path('Actions/Fortify/...')`, so moving them means a later `php artisan fortify:install` — or a starter-kit update — re-publishes them and leaves two classes with the same name, only one of which is bound.
- **A listener is not an action.** A class whose job is to handle a framework event belongs in `app/Listeners/` and keeps Laravel's default `handle()` method, which is what a bare class-string `Event::listen()` registration resolves to. Renaming it to `execute()` to satisfy this convention breaks the registration — and either way it was in the wrong namespace.

### 3. Fixed Dependencies vs. Variable Parameters

Actions have two types of inputs:

**Fixed Dependencies** (Constructor Injection):

- Dependencies that don't change across calls
- Injected via the constructor
- Resolved automatically by Laravel's container
- Examples: other actions, services

**Variable Parameters** (Execute Method):

- Data that changes between calls
- Passed to the `execute()` method
- Enable the same action instance to be reused with different inputs
- Example: `BuildOpportunities` receives `$priceBreaks` as a parameter

```php
class BuildOpportunities
{
    // Fixed dependency: injected in constructor
    public function __construct(
        private FetchPropertiesForPriceBreak $fetchProperties,
        private MapPropertiesToOpportunities $mapToOpportunities,
    ) {}

    // Variable parameter: passed to execute()
    public function execute(array $priceBreaks): array
    {
        // Use both fixed dependencies and variable parameters
    }
}
```

### 4. Composability and Container Resolution

Actions are composable — they can depend on other actions:

- All fixed dependencies must be resolvable from Laravel's container by type alone
- Actions can depend on other actions via constructor injection
- **Avoid circular dependencies** — actions should form a directed acyclic graph
- The container automatically resolves dependencies; explicit instantiation is rarely needed

### 5. Statelessness and Readonly Classes

Actions must be **stateless**. They should not maintain instance properties that affect behavior across multiple `execute()` calls.

**Why**: If you create an action and call `execute()` twice with different parameters, the second call should not be affected by the first.

**Implementation**: Action classes should be declared as `readonly` since they are stateless and only hold immutable dependencies:

```php
final readonly class BuildOpportunities
{
    public function __construct(
        private FetchPropertiesForPriceBreak $fetchProperties,
        private MapPropertiesToOpportunities $mapToOpportunities,
    ) {}

    public function execute(array $priceBreaks): array
    {
        // ...
    }
}
```

The `readonly` keyword enforces immutability at the class level and makes the stateless nature explicit.

## When to Use the Action Pattern

Activate this skill when:

- **Extracting logic from controllers**: Business logic belongs in actions, not controllers
- **Creating new actions**: Ensure they follow the pattern correctly
- **Refactoring code that violates SRP**: If a class is doing multiple things, consider splitting it into multiple actions
- **Code is required by multiple parts of the application**: If behavior is needed in multiple places, extract it into an action

## Controller Integration

Controllers should delegate business logic to actions. Actions are injected into controller **methods**, not constructors, unless the action is used by **every** public method in the controller.

**Rule**: Only inject an action into the constructor if it's used by all public methods. Otherwise, inject it into the specific methods that need it.

From ceilidhboy/skills, MIT.

More from ceilidhboy/skills

Signals

Stars
0
Forks
0
Open issues
0
Last commit (this folder)
Sep 25, 2026 (1 month ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from ceilidhboy

Cite this page

action-pattern skill. Agent Mart. https://agentmart.dev/skills/ceilidhboy-action-pattern (last updated 2026-10-24)