# Actions & Filters

> Register WordPress actions and filters in Pollora with PHP 8 attributes or the Action and Filter facades, including deferred callbacks and service access.

Source: https://pollora.dev/hooks/actions-filters/

- [Attribute-based hooks](#attribute-based-hooks)
  - [Generate with Artisan](#generate-with-artisan)
  - [Action hooks](#action-hooks)
  - [Filter hooks](#filter-hooks)
- [Facades](#action-and-filter-facades)
  - [Registering an action or filter](#registering-an-action-or-filter)
  - [Using functions, closures, or instances](#using-functions-closures-or-instances)
  - [Constructor injection](#constructor-injection)
  - [Executing actions and filters](#executing-actions-and-filters)
  - [Checking existence](#checking-existence)
  - [Removing actions and filters](#removing-actions-and-filters)
  - [Retrieving callbacks](#retrieving-callbacks)
- [Service access](#service-access)

## Attribute-based hooks

You can define action and filter hooks using PHP attributes. This provides a more concise way to define hooks within your classes.

### Generate with Artisan

You can create a new attribute-based hook class using the `pollora:make:action` or `pollora:make:filter` Artisan commands:

```bash
# Create a new action hook class
php artisan pollora:make:action MyActionClass

# Create a new filter hook class
php artisan pollora:make:filter MyFilterClass
```

### Action hooks

To define an action hook, use the `Action` attribute from the `Pollora\Attributes\Action` namespace:

```php
<?php

namespace App\Cms\Hooks;

use Pollora\Attributes\Action;

class MyActionClass
{
    #[Action('init', priority: 20)]
    public function handleInit(): void
    {
        // Code to execute for the 'init' WordPress action
    }
}
```

Multiple action hooks can be declared for the same method:

```php
#[Action('init', priority: 10)]
#[Action('wp_loaded', priority: 15)]
public function setup(): void
{
    // Runs on multiple hooks
}
```

### Filter hooks

To define a filter hook, use the `Filter` attribute from the `Pollora\Attributes\Filter` namespace:

```php
<?php

namespace App\Cms\Hooks;

use Pollora\Attributes\Filter;

class MyFilterClass
{
    #[Filter('the_content', priority: 10)]
    public function handleTheContent(string $content): string
    {
        return str_replace('ugly', 'shiny', $content);
    }
}
```

All classes inside the `app/Cms/Hooks` folder with hook attributes are automatically discovered and registered.

## Action and Filter facades

Pollora provides two powerful facades, `Action` and `Filter`, to interact with WordPress hooks in a structured and maintainable way. These facades simplify the registration, execution, and management of actions and filters, while ensuring dependency injection and class-based organization.

### Registering an action or filter

To register an action or filter, use the respective `add()` method:

```php
use Pollora\Support\Facades\Action;
use Pollora\Support\Facades\Filter;

// Register an action
Action::add('init', [App\Hooks\InitHandler::class, 'boot']);

// Register a filter explicitly specifying the method
Filter::add('the_content', [App\Hooks\ContentHandler::class, 'modify']);

// Register a filter without specifying the method
Filter::add('the_content', App\Hooks\ContentHandler::class);
```

When passing a class reference alone, Pollora will automatically call a method following Laravel's naming conventions: it will look for `theContent()` inside `ContentHandler`. If a method is explicitly provided, that method will be called instead. If the method is not found, an exception is thrown, specifying the missing method.

### Using functions, closures, or instances

Actions and filters can also be registered using functions, closures, or object instances:

```php
// Function
function custom_callback() {
    // Code...
}
Action::add('init', 'custom_callback');

// Closure
Filter::add('the_content', function($content) {
    return strtoupper($content);
});

// Object instance
$handler = new App\Hooks\ContentHandler();
Filter::add('the_content', [$handler, 'modify']);
```

### Constructor injection

Pollora automatically resolves dependencies when registering actions and filters using a class reference. Both the attribute-based discovery path and the imperative API (facades) support constructor injection through the Laravel container:

```php
namespace App\Cms\Hooks;

use Illuminate\Http\Request;
use App\Services\ContentProcessor;

class ContentHandler {
    protected $request;
    protected $processor;

    public function __construct(Request $request, ContentProcessor $processor) {
        $this->request = $request;
        $this->processor = $processor;
    }

    public function theContent($content) {
        return $this->processor->process($content);
    }

    public function modify($content) {
        return strtoupper($content);
    }
}
```

This class can be registered in two ways:

```php
// Calls the `theContent` method automatically
Filter::add('the_content', App\Hooks\ContentHandler::class);

// Calls the `modify` method explicitly
Filter::add('the_content', [App\Hooks\ContentHandler::class, 'modify']);
```

### Executing actions and filters

To execute an action, use `do()`, which functions like `do_action()` in WordPress:

```php
Action::do('custom_event', $arg1, $arg2);
```

To apply a filter, use `apply()`, similar to `apply_filters()`:

```php
$modified_value = Filter::apply('custom_filter', $original_value, $arg1);
```

### Checking existence

To verify if an action or filter is registered:

```php
if (Action::exists('custom_event')) {
    // Code...
}

if (Filter::exists('custom_filter')) {
    // Code...
}
```

### Removing actions and filters

Actions and filters can be removed using `remove()`:

```php
Action::remove('init', [App\Hooks\InitHandler::class, 'boot']);
Filter::remove('the_content', [App\Hooks\ContentHandler::class, 'modify']);
```

> **Note:** If an action or filter was registered with a specific priority, the same priority must be specified when removing it.

### Retrieving callbacks

To get the registered callbacks for an action or filter:

```php
$callbacks = Action::callbacks('init');
$filterCallbacks = Filter::callbacks('the_content');
```

## Asynchronous actions

An action can run after the request instead of inside it: add `->async()` after `Action::add()`, or `#[Async]` next to `#[Action]`. See [Asynchronous Actions](https://pollora.dev/hooks/async-actions/).

## Deferred callbacks

Pollora aligns with WordPress behavior for callback registration: callbacks that are not yet callable at registration time (e.g., functions defined by plugins loaded later) are accepted without throwing an exception. Validation occurs at execution time, just like WordPress's native `add_action()`/`add_filter()`.

```php
// This works even if the function doesn't exist yet —
// it will be resolved when the hook fires.
Action::add('init', 'my_plugin_function');
```

## Service access

The `Action` and `Filter` services are registered in the container and can be accessed via facades or dependency injection:

```php
// Via facades (recommended for most use cases)
use Pollora\Support\Facades\Action;
use Pollora\Support\Facades\Filter;

// Via dependency injection (for services that need hook contracts)
use Pollora\Hook\Domain\Contract\Action as ActionContract;
use Pollora\Hook\Domain\Contract\Filter as FilterContract;

class MyService {
    public function __construct(
        private ActionContract $action,
        private FilterContract $filter
    ) {}
}
```

> **Extension author API:** The `Pollora\Hook\Domain\Contract\Action` and `Pollora\Hook\Domain\Contract\Filter` interfaces are **stable public contracts** for services that need to register hooks programmatically via dependency injection. For most use cases, prefer the facades above.

## See also

- [Asynchronous Actions](https://pollora.dev/hooks/async-actions/)
- [WordPress hooks with PHP 8 attributes](https://pollora.dev/guides/wordpress-hooks-php-attributes/)
- [How Pollora runs WordPress inside Laravel](https://pollora.dev/guides/how-pollora-runs-wordpress-inside-laravel/)
