Download the PHP package icanboogie/event without Composer
On this page you can find all versions of the php package icanboogie/event. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download icanboogie/event
More information about icanboogie/event
Files in icanboogie/event
Package event
Short Description Provides an event API
License BSD-3-Clause
Homepage https://icanboogie.org/
Informations about the package event
Event
The icanboogie/event package lets your code emit events, and lets other developers attach listeners to be notified when they occur and take action.
Inside ICanBoogie, events are often used to alter initial parameters, take action before/after an operation is processed or when it fails, take action before/after a request is dispatched or to rescue an exception.
Installation
Feature highlights
- Events are typed.
- Events usually have a sender object, but simpler event types can also be emitted.
- Event listeners are attached to classes rather than objects, and they are inherited.
- Event listeners can be attached at runtime, with a configuration builder, or discovered with the
#[Listener]attribute. - Event listeners can be attached to a finish chain that is executed after the event listeners chain.
- Execution of the event chain can be stopped.
A twist on the Observer pattern
The pattern used by the API is similar to the Observer pattern, although instead of attaching event listeners to objects they are attached to their class. When an event is fired by a sender object, the hierarchy of its class is used to filter event listeners.
Consider the following class hierarchy:
App\Document
└─ App\Invoice
└─ App\RecurringInvoice
When a SavedEvent is emitted with a RecurringInvoice instance, all event listeners attached to
the classes for this event are called, starting from the event listeners attached to the instance
class (RecurringInvoice) all the way up to those attached to its root class (Document).
Thus, event listeners attached to the Invoice class are called when a SavedEvent is emitted with
a RecurringInvoice instance. One could consider that event listeners are inherited.
[!NOTE] Listeners attached to the sender's class come first, then those attached to its parent classes (
get_parent_class()), and finally those attached to the interfaces it implements (class_implements()), so a listener can be attached to an interface such asThrowable. See Events with a sender.
Getting started
Events are emitted with emit(), which uses the current event collection, the object that holds
the listeners. A collection is created for you when required, so you can attach listeners and emit
events right away. Define a provider when you want the collection to start with listeners, for
instance the ones of a config:
Typed events
Events are subclasses of the Event class. The simplest event has no sender and no data, its class is its type:
An event is about something, an object that is its sender, and carries data. The sender is passed
to parent::__construct(). The following code demonstrates how a SavedEvent class may be defined,
for the Document objects of an application:
Event types
If an event has a sender, its qualified type is obtained using the for() method and the sender
class or object, e.g. App\Document::App\Document\SavedEvent. If an event doesn't have a
sender, the event type is the event class, e.g. App\CacheClearedEvent.
The sender is the object passed to parent::__construct(). It is available as $event->sender,
only when the event was constructed with one.
Namespacing and naming
Event classes should be defined in a namespace unique to their sender object. Events sent by
App\Document instances should be defined in the App\Document namespace, e.g.
App\Document\SavedEvent.
Firing events
Events are fired with the emit() function.
Attaching event listeners
Event listeners are attached using the attach() method of an event collection. The attach()
method is smart enough to create the event type from the parameter types.
- A listener with a single parameter (
fn(MyEvent $event)) is attached to the bare event type,MyEvent. It is called for everyMyEvent, whatever the sender, after the listeners attached to the sender. - A listener with two parameters (
fn(MyEvent $event, Sender $sender)) is attached to the qualified typeSender::MyEvent. It is called for events emitted with a sender of that class, or one of its subclasses.
Listeners attached with attach() are called in reverse order: the last one attached is called
first. Listeners of a config keep the order they were given.
The following example demonstrates how a closure may be attached to a SavedEvent event.
The following example demonstrates how an invokable object may be attached to that same event type.
Attaching an event listener to a specific sender
Using the attach_to() method, an event listener can be attached to a specific sender instance, and
is only invoked for that sender.
Attaching a one-time event listener
The once() method attaches an event listener that is automatically detached after it's been used.
Attaching event listeners using a config
Event listeners can be declared in an ICanBoogie\Event\Config, created with ConfigBuilder, and
given to the EventCollection constructor or to attach_many(). A Config can be exported with
var_export(), to be cached as PHP code.
When the package is bound to ICanBoogie by icanboogie/bind-event, the config is built from
the event config fragment (e.g. app/all/config/event.php), using the same ConfigBuilder.
See the README of icanboogie/bind-event for the fragment, services, and troubleshooting.
[!NOTE] With
ConfigBuilder, listeners are callables. To reference a service, useICanBoogie\Service\ref(). The service must be public, otherwise the failure only occurs when the first event is emitted.
Using the #[Listener] attribute
enable_discovery() makes build() look for the methods marked with ICanBoogie\Event\Listener,
and attach them after the listeners attached explicitly. Nothing is collected until build() is
invoked. disable_discovery() turns it off again, discovery is off by default.
Discovery relies on olvlvl/composer-attribute-collector, which your application has to require.
Load the vendor/attributes.php file it generates before building the config, see its
documentation.
- Static methods are attached as
[class, method]. Non-static methods must be__invoke(), and are resolved as services (the class by default, or#[Listener(ref: 'service_id')]). - One parameter attaches to the bare event type, two parameters attach to the event type of the sender class or interface (given by the second parameter, which must be loadable).
- There is one attribute per method, and a method names a single sender type. To listen to several
unrelated senders, use
attach_to()once per type on the config builder. - The config can't be built, and a
LogicExceptionis thrown, if the collector is not available, a non-static method is not__invoke(), a method has more than two parameters, or the parameter types are not suitable (the first must be anEvent).
Attaching event listeners to the finish chain
The finish chain is executed after the event chain was traversed without being stopped.
The following example demonstrates how an event listener may be attached to the finish chain of
the CountEvent event to obtain the string "0123". Listeners attached with attach() are called in
reverse order, so the third one is called first and registers "3" in the finish chain. If it was
defined like the others we would obtain "0312".
Breaking an event listener chain
The processing of an event listener chain can be broken by an event listener using the stop()
method. The listeners that follow in the chain are not called, nor are the listeners of the finish
chain.
Events with a sender
Events with a sender are the most common source of confusion.
The qualified type includes the sender class
The sender is whatever object the event passes to parent::__construct():
This event is emitted with the type RuntimeException::MyEvent (sender class, then event class),
not MyEvent. A one-parameter listener, fn(MyEvent $event), or
ConfigBuilder::attach(MyEvent::class, …), is attached to the bare MyEvent and catches every
MyEvent, whatever its sender. To only catch the events of a given sender, use a two-parameter
listener, fn(MyEvent $event, RuntimeException $sender), attach_to(), or
ConfigBuilder::attach_to().
Matching the sender's class or interfaces
Listeners are matched against the sender's class, its parent classes, then its interfaces.
ConfigBuilder::attach_to() requires a loadable class or interface. Exceptions and errors have two
class roots (Exception and Error), but both implement Throwable, which catches them all:
Checking the wiring
A unit test of the listener proves nothing about registration. Emit the event with a real sender,
then compare $event->qualified_type with $events->get_listeners($event->qualified_type), or look
at EventProfiler::iter_unused(), which lists the types that were emitted with no listener.
Events without a sender are simpler to use: consider whether your event really needs one.
Profiling events
The EventProfiler class is used to collect timing information about unused events and event listener calls. All time information is measured in floating microtime.
An unused event is an emitted event with no attached listeners. If a listener you expected isn't called, check that its event type shows up there.
Helpers
get_events(): Returns the current event collection. A new one is created if none exist.emit(): Emit the specified event.
Continuous Integration
The project is continuously tested by GitHub Actions.
Code of Conduct
This project adheres to a Contributor Code of Conduct. By participating in this project and its community, you're expected to uphold this code.
Contributing
See CONTRIBUTING for details.