Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laminas Eventmanager Laravel Package

laminas/laminas-eventmanager

Laminas EventManager provides a flexible event and listener system for PHP applications. Attach listeners, trigger events, manage priorities, and use shared event managers to coordinate decoupled components across your app.

View on GitHub
Deep Wiki
Context7

Intercepting Filters

Intercepting filters are a design pattern used for providing mechanisms to alter the workflow of an application. Implementing them provides a way to have a standard public interface, with the ability to attach arbitrary numbers of filters that will take the incoming arguments in order to alter the workflow.

laminas-eventmanager provides an intercepting filter implementation via Laminas\EventManager\FilterChain.

Preparation

To use the FilterChain implementation, you will need to install laminas-stdlib, if you have not already:

$ composer require laminas/laminas-stdlib

FilterChainInterface

Laminas\EventManager\FilterChain is a concrete implementation of Laminas\EventManager\Filter\FilterInterface, which defines a workflow for intercepting filters. This includes the following methods:

interface FilterInterface
{
    public function run($context, array $params = []);

    public function attach(callable $callback);
    public function detach(callable $callback);

    public function getFilters();
    public function clearFilters();
    public function getResponses();
}

In many ways, it's very similar to the EventManagerInterface, but with a few key differences:

  • A filter essentially defines a single event, which obviates the need for attaching to multiple events. As such, you pass the target and parameters only when "triggering" (run()) a filter.
  • Instead of passing an EventInterface to each attached filter, a FilterInterface implementation will pass:
    • The $context
    • The $params
    • A FilterIterator, to allow the listener to call on the next filter.

FilterIterator

When executing run(), a FilterInterface implementation is expected to provide the stack of attached filters to each listener. This stack will typically be a Laminas\EventManager\Filter\FilterIterator instance.

FilterIterator extends Laminas\Stdlib\FastPriorityQueue, and, as such, is iterable, and provides the method next() for advancing the queue.

As such, a listener should decide if more processing is necessary, and, if so, call on $chain->next(), passing the same set of arguments.

Filters

A filter attached to a FilterChain instance can be any callable. However, these callables should expect the following arguments:

function ($context, array $argv, FilterIterator $chain)

A filter can therefore act on the provided $context, using the provided arguments.

Part of that execution can also be deciding that other filters should be called. To do so, it will call $chain->next(), providing it the same arguments:

function ($context, array $argv, FilterIterator $chain)
{
    $message = isset($argv['message']) ? $argv['message'] : '';

    $message = str_rot13($message);

    $filtered = $chain->next($context, ['message' => $message], $chain);

    return str_rot13($filtered);
}

You can choose to call $chain->next() at any point in the filter, allowing you to:

  • pre-process arguments and/or alter the state of the $context.
  • post-process results and/or alter the state of the $context based on the results.
  • skip processing entirely if criteria is not met (e.g., missing arguments, invalid $context state).
  • short-circuit the chain if no processing is necessary (e.g., a cache hit is detected).

Execution

When executing a filter chain, you will provide the $context, which is usually the object under observation, and arguments, which are typically the arguments passed to the method triggering the filter chain.

As an example, consider the following filter-enabled class:

use Laminas\EventManager\FilterChain;

class ObservedTarget
{
    private $filters = [];

    public function attachFilter($method, callable $listener)
    {
        if (! method_exists($this, $method)) {
            throw new \InvalidArgumentException('Invalid method');
        }
        $this->getFilters($method)->attach($listener);
    }

    public function execute($message)
    {
        return $this->getFilters(__FUNCTION__)
            ->run($this, compact('message'));
    }

    private function getFilters($method)
    {
        if (! isset($this->filters[$method])) {
            $this->filters[$method] = new FilterChain();
        }
        return $this->filters[$method];
    }
}

Now, let's create an instance of the class, and attach some filters to it.

$observed = new ObservedTarget();

$observed->attach(function ($context, array $args, FilterIterator $chain) {
    $args['message'] = isset($args['message'])
        ? strtoupper($args['message'])
        : '';

    return $chain->next($context, $args, $chain);
});

$observed->attach(function ($context, array $args, FilterIterator $chain) {
    return (isset($args['message'])
        ? str_rot13($args['message'])
        : '');
});

$observed->attach(function ($context, array $args, FilterIterator $chain) {
    return (isset($args['message'])
        ? strtolower($args['message'])
        : '');
});

Finally, we'll call the method, and see what results we get:

$observed->execute('Hello, world!');

Since filters are run in the order in which they are attached, the following will occur:

  • The first filter will transform our message into HELLO, WORLD!, and then call on the next filter.
  • The second filter will apply a ROT13 transformation on the string and return it: !DLROW ,OLLEH.

Because the second filter does not call $chain->next(), the third filter never executes.

Notes

We recommend using the construct run($this, compact(method argument names) when invoking a FilterChain. This makes the argument keys predictable inside filters.

We also recommend putting the default logic for the method invoking the filter chain in a filter itself, and attaching it at invocation. This allows intercepting filters to replace the main logic, while still providing a default path. This might look like:

// Assume that the class contains the `attachFilter()` implementation from above.
class ObservedTarget
{
    private $attached = [];

    public function execute($message)
    {
        if (! isset($this->attached[__FUNCTION__])) {
            $this->attachFilter(__FUNCTION__, $this->getExecuteFilter();
        }

        return $this->getFilters(__FUNCTION__)
            ->run($this, compact('message'));
    }

    private function getExecuteFilter()
    {
        $this->attached['execute'] = true;
        return function ($context, array $args, FilterIterator $chain) {
            return $args['message'];
        };
    }
}

Intercepting filters are a powerful way to introduce aspect-oriented programming paradigms into your code, as well as general-purpose mechanisms for introducing plugins.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
davejamesmiller/laravel-breadcrumbs
artisanry/parsedown
christhompsontldr/phpsdk
enqueue/dsn
bunny/bunny
enqueue/test
enqueue/null
enqueue/amqp-tools
bower-asset/punycode
bower-asset/inputmask
bower-asset/jquery
bower-asset/yii2-pjax
laravel/nova
spatie/laravel-mailcoach
spatie/laravel-superseeder
laravel/liferaft
nst/json-test-suite
danielmiessler/sec-lists
jackalope/jackalope-transport
twbs/bootstrap4