Download the PHP package snicco/better-wp-hooks without Composer
On this page you can find all versions of the php package snicco/better-wp-hooks. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Informations about the package better-wp-hooks
BetterWPHooks - A 2022 redesign of the WordPress hooks system (PSR-14-compatible)
BetterWPHooks is a small library that allows you to write modern, testable and object-oriented code in complex WordPress projects.
Table of contents
- Motivation
- Installation
- Usage
- Event listeners
- Dispatching events
- The Event interface
- Event subscribers
- Removing listeners
- Mapping events to core/third-party hooks
- Ensuring your event fires first
- Ensuring your event fires last
- Exposing (some of) your events to WordPress
- A better alternative to apply_filters
- Stopping event flow/propagation
- Testing
- Contributing
- Issues and PR's
- Security
Motivation
BetterWPHooks is a central component in the Snicco project and was developed because the [WordPress hook system]( ) suffers the following problems:
- You have no type-safety at all when using
add_action
andadd_filter
. Anything can be returned. - An event (hook) should ideally be immutable, meaning that it can't be changed. Using
apply_filters
the original arguments are immediately lost as soon as the first callback is run. - There is no proper place to define hooks and callbacks. Many developers default to putting hooks into the class constructor which is a bad solution for many reasons.
- Dependency injection is not supported. You can't lazily instantiate class callbacks.This leads to either massive pollution of the global namespace with custom functions, or instantiating all classes of a codebase on each and every request. Not quite performant.
- There is no way to define which hooks are for public usage and which one are internal to your codebase.
- It's extremely difficult to remove hooks that are registered as closures or object methods.
- It's very hard to test hooks without using additional test frameworks like WP_Mock or Brain Monkey . (mocking sucks)
While throwing in a quick action here and there is completely fine for small projects, for enterprise level projects or complex distributed plugins WordPress hooks become a maintenance and testability burden.
Installation
Usage
Creating an event dispatcher
By default, your event listeners (WordPress calls them hook callbacks) are assumed to be newable classes ($instance = new MyClass()
).
Optionally (but strongly recommended), you can resolve your listeners using any PSR-11 container.
Event listeners
These are the valid ways to attach listeners to any event:
Dispatching events
Any event is dispatched by using the dispatch
method on your WPEventDispatcher
instance.
The dispatch method accepts any object
. By default, the class name of the event will be used to determine the
listeners that should be created and called.
Since BetterWPHooks is PSR-14 compliant, every call to dispatch
will return the same object instance that was
being passed.
You can create generic events on the fly if for some reason you don't want to create a dedicated event class:
The first constructor argument of GenericEvent
is the event name, the second one an array of arguments that will be
passed to all listeners.
The Event
interface
BetterWPHooks comes with an interface that you can use to fully customize the behaviour of your events.
Assuming the OrderCreated
event implements this interface
:
Your code would now look like this:
Event Subscribers
Instead of defining all your listeners using the listen
method you can also implement the EventSubscriber
interface
and use the subscribe
method on the WPEventDispatcher
.
Removing event listeners
In most cases, your event dispatcher should be immutable after the bootstrapping phase of your application/plugin. If however you want to remove events/listeners you can do it like so:
If you want to prevent the removal of a specific listener you can implement the Unremovable
interface. If an
unremovable listener is being removed an CantRemoveListener
exception will be thrown.
Mapping core and third party actions.
BetterWPHooks comes with a very useful EventMapper
class. The EventMapper
allows you transform WordPress
core or other third-party actions/filters into proper event objects.
It serves as a thin layer in between your code and external hooks.
Mapped events MUST either implement MappedHook
or MappedFilter
Implement MappedHook
if you are mapping your event to and action, MappedFilter
if you are mapping to a filter
that expects are return value.
Utilizing the EventMapper
, you get to keep all the benefits of BetterWPHooks like lazy-loading your listeners
while still being able to interacts with third-party code the same way as before.
The shouldDispatch
method on the MappedHook
interface gives you great control over your event flow.
If shouldDispatcher
returns (bool) false
all attached listeners will not be called.
This allows you to build highly customized and performant integrations with third-party code.
An example for mapping to an action:
(This event will only be dispatched if the user performing the order is logged in)
An example for mapping to a filter:
(This event will always be dispatched since we return true
)
Ensuring your mapped event fires first
Using the mapFirst
method on the EventMapper
your event listeners will always be run before any other
hook callbacks registered with WordPress.
Ensuring your mapped event fires last
Using the mapLast
method on the EventMapper
your event listeners will always be run after any other
hook callbacks registered with WordPress. This is especially useful for filters where you want to control the final
result.
Exposing (some of) your events to the WordPress hook system
The WordPress hook system is globally available. This is a problem. Both your code as a developer and for users who want to interact with the custom events created by your application/plugin.
There is no way to enforce which events are safe to rely upon and which events might disappear tomorrow because you refactored your code.
The ExposeToWP
interface helps with this.
By default, every time you dispatch an event your internal listeners will be called first.
If the dispatched event object implements the ExposeToWP
interface the event object will be passed
to the WordPress hook system so that third-party developers can interact with your code within the scope that you define.
If the dispatched event object does not implement ExposeToWP
it will not be available to WordPress hooks.
An example:
A better alternative to apply_filters
The PSR-14 meta documentation defines four common goals of an event system:
- One-way notification. ("I did a thing, if you care.")
- Object enhancement. ("Here's a thing, please modify it before I do something with it.")
- Collection. ("Give me all your things, that I may do something with that list.")
- Alternative chain. ("Here's a thing; the first one of you that can handle it do so, then stop.")
Most of the time using apply_filters
in your code means that you want to enhance behaviour or allow other developers
to customize the behaviour of your code. (Object enhancement)
apply_filters
is not ideal for this as its return type is mixed
.
There is nothing stopping a third-party developer mistakenly returning (int) 0
when you are expecting (bool) false
.
Event objects allow you to enforce type-safety so that you don't have to manually type-check the end-result of every filter.
Here is what we recommend and use in our code:
Stopping event flow/propagation
In some cases, it may make sense for a listener to prevent any other listeners from being called. In other words, the listener needs to be able to tell the dispatcher to stop all propagation of the event to future listeners (i.e. to not notify any more listeners).
In order for this to work your event object must implement the PSR-14 StoppableEventInterface.
An example:
Testing
BetterWPHooks comes with dedicated testing utilities for phpunit.
First, install:
This package should be installed as dev dependency
with composer. It's not intended for production use.
Now, in your tests, you should wrap your configured WPEventDispatcher
with the TestableEventDispatcher
.
How you do that depends on how you structured your codebase.
The TestableEventDispachter
wraps the WPEventDispatcher
and can make assertions about the dispatched events in your tests.
Furthermore, you can fake events so that they will not be passed to the real WPEventDispatcher
.
The dispatch
, listen
, subscribe
, remove
methods will be proxied to the WPEventDispatcher
.
The following assertions methods are available.
Certain events can be faked like this:
Contributing
This repository is a read-only split of the development repo of the Snicco project.
This is how you can contribute.
Reporting issues and sending pull requests
Please report issues in the Snicco monorepo.
Security
If you discover a security vulnerability within BetterWPHooks, please follow our disclosure procedure.
All versions of better-wp-hooks with dependencies
ext-json Version *
snicco/better-wp-api Version ^2.0
snicco/event-dispatcher Version ^2.0