PHP code example of calvinalkan / better-wordpress-hooks
1. Go to this page and download the library: Download calvinalkan/better-wordpress-hooks library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
calvinalkan / better-wordpress-hooks example snippets
// Approach #1, creating the class and passing the object to the hook.
class MyClass {
public function __construct() {
add_action('init', [ $this, 'doStuff']);
}
public function doStuff () {
//
}
public static function doStuffStatic () {
//
}
}
// Approach #2, Using static functions, worse.
add_action('init', [ MyClass::class, 'doStuffStatic' ]);
//Let's assume we want to send an email and log to an external service
// ( maybe a google sheet ) every time an order takes place with a total > 500.
class GiftCardHandler {
private $mailer;
private $logger;
// We need a mailer and a logger service
public function __construct( Mailer $mailer, Logger $logger) {
$this->mailer = $mailer;
$this->logger = $logger;
add_action('checkout_completed', [ $this, 'handle'], 10, 1);
}
//Let's assume we get an order object from the hook.
public function handle (Order $order) {
if ( $order->total() >= 500 ) {
$mailer->sendGiftcard($order->user());
$logger->logBigBurchase($order);
}
}
}
use BetterWpHooks\Traits\BetterWpHooksFacade;
class AcmeEvents {
use BetterWpHooksFacade;
}
use BetterWpHooks\Traits\BetterWpHooksFacade;
class AcmeEvents {
use BetterWpHooksFacade;
}
AcmeEvents::make();
// Alternatively if you want to use a custom container:
$custom_container_adapter = new SymfonyContainerAdapter();
AcmeEvents::make($custom_container_adapter);
$mapped = [
// Will fire on priority 10
'init' => RegisterJobListingPostType::class,
// Will fire on priority 99
'save_post_job_listing' => [JobListingCreated::class, 99],
// Will be resolved from the service container
// and fire on priority 99.
'booking_created' => ['resolve' ,BookingCreated::class, 99],
];
AcmeEvents::mapEvents($mapped);
$listeners = [
RegisterJobListingPostType::class => [
PostTypeRegistry::class,
// More Event Listeners if needed
],
JobListingCreated::class => [
NotifyAdmin::class,
SendConfirmationEmail::class,
TagCreatorInMailchimp::class
// More Event Listeners if needed
],
// Custom Events that you fire in your code
JobMatchFound::class => [
NotifyListingOwner::class,
NotifyApplicatant::class,
]
];
AcmeEvents::listeners($listeners);
AcmeEvents::boot();
class HighValueOrderCreated extends AcmeEvents {
public $order;
public $submission_data;
public function __construct( int $order_id, array $submission_data ) {
$this->order = wc_get_order($order_id);
$this->submission_data = $submission_data;
}
}
class ProcessGiftCards {
private $mailer;
private $logger;
public function __construct( $mailer, $logger ) {
$this->mailer = $mailer;
$this->logger = $logger;
}
public function handleEvent ( HighValueOrderCreated $event) {
$order = $event->order;
// Example properties.
$this->mailer->sendGiftCard( $order->user_id, $order->items );
$this->logger->logGiftcardRecepient( $order->user_id, $order->items, $order->purchased_at );
}
}
$mapped = [
'woocommerce_checkout_order_processed' => [
HighValueOrderCreated::class
// Map more if needed.
]];
$listeners = [
HighValueOrderCreated::class => [
ProcessGiftCards::class . '@handleEvent'
// More Listeners if needed
] ];
AcmeEvents::make()->mapEvents($mapped)->listeners($listeners)->boot();
class HighValueOrderCreated extends AcmeEvents {
use \BetterWpHooks\Traits\DispatchesConditionally;
public $order;
public $submission_data;
public function __construct( int $order_id, array $submission_data ) {
$this->order = wc_get_order($order_id);
$this->submission_data = $submission_data;
}
public function shouldDispatch() : bool{
return $this->order->total >= (int) get_option('acme_gift_card_amount');
}}
use Illuminate\Container\Container;
use SniccoAdapter\BaseContainerAdapter;
use MailerInterface;
$container = new Container();
$container->when(ProcessGiftCards::class)
->needs(MailerInterface::class)
->give(function () {
$mailer = get_option('acme_mailer');
if($mailer === 'sendgrid') {
return new SendGridMailer();
}
if($mailer === 'mailgun') {
return new MailGunMailer();
}
return new AwsMailer();
});
AcmeEvents::make(new BaseContainerAdapter($container)); // ->
// Code that processes an appointment booking.
do_action('booking_created', $book_id, $booking_data );
// Code that processes an appointment booking.
BookingCreated::dispatch( [$book_id, $booking_data] );
// Code that processes an appointment booking.
AcmeEvents::dispatch( 'booking_created', $book_id, $booking_data );
// These are identical.
AcmeEvents::dispatch( 'booking_created', [ $book_id, $booking_data ] );
AcmeEvents::dispatch( 'booking_created', $book_id, $booking_data );
// If we have a big-group appointment we want to send an
// email to the responsible staff to notify them in advance
// This can be replaced
if ( $appointment->participantCount() >= 5 ) {
do_action('acme_big_group_booking_created', $appointment);
}
// With
BigGroupBookingCreated::dispatchIf( $appointment->participantCount() >= 5, [$appointment]);
// Code to create an appointment object
$appointment = AppointmentCreated::dispatch([ $appointment_object ]);
// Save appointment to the Database.
// When dispatching object events the hook id is always the full class name.
add_filter('AppointmentCreated::class', function( AppointmentCreated $event) {
$appointment = $event->appointment;
if ( $some_conndition === TRUE ) {
// increase cost.
$appointment->cost = 50;
}
return $appointment;
} );
class MyEvent extends AcmeEvents {
public function default($original_payload, $filtered_value) :array {
return [$filtered_value];
}
}
add_filter(MyEvent::class, function ( $event ) {
return 'foo';
});
$value = MyEvent::dispatch();
// without array typehint $value = 'foo', whoops.
$do_stuff = $value[0];
// with typehint on default()
// $value = ['foo'];
$listeners = [
Event1::class => [
// All of these class callables will work.
Listener::class,
Listener::class . '@foo',
[ Listner::class ],
[ Listener::class, 'handleEvent' ],
[ Listener::class, 'handleEvent' ],
[ Listener::class, 'foobar' ],
[ Listener::class . '@foobarbiz' ],
[ 'custom_identifier' => Listener::class . '@foo' ],
[ 'custom_identifier' => Listener::class],
[ 'custom_identifier' => [ Listener::class, 'foobar' ] ],
// Closures
function (Event1 $event ) {
// Do Stuff
},
'custom_closure_key' => function (Event1 $event ) {
// Do Stuff
}
]
];
class BookingEventsListener {
private $complex_mailer;
public function __construct( ComplexMailerDependency $mailer ) {
$this->complex_mailer = $mailer;
}
public function bookingCanceled (BookingCanceled $event, BookingcomClient $bookingcom_client ) {
$owner = $event->booking->owner();
$guest = $event->booking->guest();
$this->complex_mailer->confirmCancelation([ $owner, $guest ]);
$bookingcom_client->updateAvailablity($event->booking_id);
}
}
class ComplexMailerDependency {
private $simple_dependency;
public function __construct( SimpleConstructorDependency $simple_dependency) {
$this->simple_dependency = $simple_dependency;
}
}
use BetterWpHooks\Traits\DispatchesConditionally;
class HighOrderValueCreated {
use DispatchesConditionally;
private $order;
public function __construct(Order $order ) {
$this->order = $order;
}
public function shouldDispatch() : bool{
return $this->order->total >= 500;
}}
// Process appointment
AppointmentCreated::dispatch([$order]);
// getting the underlying container instance.
$container = AcmeEvents::container();
// getting the underlying dispatcher instance.
$dispatcher = AcmeEvents::dispatcher();
// These two are equivalent
#1
$dispatcher = AcmeEvents::dispatcher();
$dispatcher->hasListeners('event_id_to_look_for');
#2
AcmeEvents::hasListener('event_id_to_look_for');
[ Listener1::class, '*' ] // will search for Listener1::class + any method
[ Listener1::class . '@*'] // will search for Listener1::class + any method
[ Listener1::class . '*' ] // will search for Listener1::class + any method
[ Listener1::class, 'foobar' ] // will search for Listener1::class + foobar method
[ Listener1::class . '@handleEvent'] // will search for Listener1::class + handleEvent method
[ Listener1::class, 'handleEvent' ] // will search for Listener1::class + handleEvent method
[ Listener1::class] // will search for Listener1::class + handleEvent method
// This will work.
AcmeEvents::listen( Event1::class, Listener1::class );
AcmeEvents::forgetOne( Event1::class, Listener1::class );
// This won't
AcmeEvents::listen( Event1::class, Listener1::class . '@foobar' );
AcmeEvents::forgetOne( Event1::class, Listener1::class );
// This will work.
AcmeEvents::listen( Event1::class, [ 'custom_id' => Listener1::class . '@foobar' ] );
AcmeEvents::forgetOne( Event1::class, 'custom_id' );
// This will also work with closures which is impossible with the default WordPress Plugin API
AcmeEvents::listen( Event1::class, [ 'closure_key' => function ( Event1 $event ) {
// do stuff
} ] );
AcmeEvents::forgetOne( Event1::class, 'closure_key' );
class OrderTest extends \PHPUnit\Framework\TestCase
{
/**
* Test order shipping.
*/
public function test_orders_can_be_shipped()
{
// This replaces the underlying dispatcher instance with a FakeDispatcher
AcmeEvents::fake();
// Perform order shipping...
// Assert that an event was dispatched...
AcmeEvents::assertDispatched(OrderShipped::class);
// Assert an event was dispatched twice...
AcmeEvents::assertDispatchedTimes(OrderShipped::class, 2);
// Assert an event was not dispatched...
AcmeEvents::assertNotDispatched(OrderFailedToShip::class);
// Assert that no events were dispatched...
AcmeEvents::assertNothingDispatched();
}
}
// SUT
class OrderProcess {
public function processNewOrder ($form_data) {
// Do stuff with $form_data
OrderCreated::dispatch([$order]);
StockStatusUpdated::dispatch([$order]);
}
}
// Registered Listeners
$listeners = [
OrderCreated::class => [
// Listeners you want for integration tests
],
StockStatusUpdated::class => [
UpdateSlowThirdPartyApi::class,
]
];
// Test
class OrderProcessTest extends \BetterWpHooks\Testing\BetterWpHooksTestCase {
protected function setUp() : void{
parent::setUp();
$this->setUpWp();
// You need to set up your events and listeners
$this->bootstrapAcmeEvents();
}
protected function tearDown() : void{
parent::tearDown();
$this->tearDownWp();
}
public function test_orders_can_be_processed()
{
$subject = new OrderProcess();
AcmeEvents::fake([
StockStatusUpdated::class,
]);
// All listeners for OrderCreated::class will be called.
// UpdateSlowThirdPartyApi::class will NOT be called.
$subject->processNewOrder([ // $test_data ]);
// Assertions work for both events.
AcmeEvents::assertDispatched(OrderCreated::class);
AcmeEvents::assertDispatched(StockStatusUpdated::class);
}
}
class CallbackCLass {
public function __construct() {
add_action('init', [ $this , 'doStuff']);
}
}
public function dispatch( $event, ...$payload ) {
// Here we handle mapped events conditionally.
if ( ! $this->shouldDispatch( $event ) ) {
return;
}
// Here we convert an event object so that the hook tag is the class name
// and the payload is the actual event object
// In a sense we just swap $event and $payload.
[ $event, $payload ] = $this->parseEventAndPayload( $event, $payload );
// Here we handle temporary removal if a listener wants to stop
// a listener chain.
$this->maybeStopPropagation( $event );
// If no listeners are registered we just return the default value.
if ( ! $this->hasListeners( $event ) ) {
if ( is_callable( [ $payload, 'default' ] ) ) {
return $payload->default();
}
return is_object( $payload ) ? $payload : $payload[0];
}
// If we make it this far, only here do we hit the WordPress Plugin API.
return $this->hook_api->applyFilter( $event, $payload );
}
BookingCreated::dispatch([$booking_data]);
// Traditional Wordpress implementation.
$booking = new Booking($booking_data);
add_action('acme_booking_created', $booking );
/**
* Wraps the created abstract listener in a closure.
* The WordPress Hook Api will save this closure as
* the Hook Callback and execute it at runtime.
*
* @param \BetterWpHooks\Contracts\AbstractListener $listener
*
* @return \Closure
*/
private function wrap( AbstractListener $listener ): Closure {
// This anonymous function will be executed by Wordpress
// Not the Listener directly.
return function ( $payload ) use ( $listener ) {
try {
return $listener->shouldHandle( $payload ) ? $listener->execute( $payload ) : $payload;
}
catch ( \Throwable $e ) {
$this->error_handler->handle($e);
}
};
}
if ( ! function_exists('acme_remove_filter') {
function acme_remove_filter($tag, $callback) {
AcmeEvents::forgetOne($tag, $callback);
}
}
// Third-party dev that wants to customise AcmeEvents
acme_remove_filter(Event1::class, Listener1::class);
// This works.
add_filter(Event1::class, ThridPartyListener::class)
woocommerce_checkout_order_processed
FALSE
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.