Recipe: Extend with a Provider
Goal: Add a service to Eventonomy's container using the auto-discovery provider pattern - the same pattern every Pro feature uses. The provider wires itself into the container with no changes to shared registrar files.
Seams Used
| Seam | Type | Purpose |
|---|---|---|
evnm_register_services |
Action | Fires after the container loads all providers; your callback receives $container. |
$container->bind() |
Method | Replace the container's one binding for a contract. Later bindings win (Pro layers on Free this way). |
$container->tag() |
Method | Add an implementation to a collection (e.g. add a gateway alongside existing ones). |
evnm() |
Function | Public container accessor - resolve any bound contract anywhere. |
When to Use bind() vs tag()
| Method | Use when |
|---|---|
bind( Contract::class, MyImpl::class ) |
You want to replace the single bound implementation. E.g. swap the recurrence engine. |
tag( Contract::class, MyImpl::class ) |
You want to add to a collection. E.g. add a payment gateway alongside Stripe. |
Example - Adding a Payment Gateway
<?php
// my-addon/includes/MyGateway.php
namespace MyAddon;
class MyGateway implements \Eventonomy\Contracts\PaymentGatewayInterface {
public function id(): string { return 'my-gateway'; }
public function label(): string { return 'Pay at Door'; }
public function supported_currencies(): array { return [ 'USD', 'EUR' ]; }
public function charge( array $order, array $args ): array {
// Record the promise; no charge collected now.
return [ 'txn_id' => 'door-' . $order['id'], 'status' => 'completed' ];
}
public function capture( string $txn, array $args ): array {
return [ 'txn_id' => $txn, 'status' => 'completed' ];
}
public function refund( string $txn, int $cents ): bool {
// No payment was collected, so nothing to refund.
return true;
}
}
<?php
// my-addon/my-addon.php (or a provider file)
add_action( 'evnm_register_services', function ( $container ): void {
// tag() adds to the gateway collection; does not replace Stripe.
$container->tag(
\Eventonomy\Contracts\PaymentGatewayInterface::class,
\MyAddon\MyGateway::class
);
} );
The gateway appears automatically in the checkout gateway selector once registered.
Example - Swapping the Recurrence Engine
add_action( 'evnm_register_services', function ( $container ): void {
// bind() replaces the single bound implementation.
$container->bind(
\Eventonomy\Contracts\RecurrenceEngineInterface::class,
\MyAddon\MyRecurrenceEngine::class
);
} );
MyRecurrenceEngine must implement handles(): array and expand($rule, $start, $window_end): array.
Using the Auto-Discovery Provider Pattern (Recommended)
The cleanest approach for plugins distributing multiple services: create a Providers/ directory and a file per provider. Eventonomy's own bootstrap scans includes/Providers/*.php via glob. You can mirror this in your plugin:
// my-addon/my-addon.php
foreach ( glob( plugin_dir_path( __FILE__ ) . 'includes/Providers/*.php' ) as $provider ) {
require_once $provider;
}
Each provider file registers its own hook:
// my-addon/includes/Providers/MyGatewayProvider.php
add_action( 'evnm_register_services', function ( $container ): void {
$container->tag(
\Eventonomy\Contracts\PaymentGatewayInterface::class,
\MyAddon\Services\MyGateway::class
);
} );
This pattern means adding a new service never requires editing a shared registrar - exactly how Pro adds features to Free.
Resolving Services from the Container
// Later in any hook - resolves the concrete from the container by contract.
$gateway_registry = evnm( \Eventonomy\Contracts\PaymentGatewayInterface::class );
// Returns an array of all tagged implementations.
$repo = evnm( \Eventonomy\Contracts\EventRepositoryInterface::class );
// Returns the single bound implementation.
Never use new on a concrete class. Always resolve through evnm().
Verify It Worked
- Activate your add-on plugin.
- For a gateway: go to Settings → Money → Payments and confirm your gateway appears in the Active gateway dropdown.
- For a service swap: write a small
wp evalcommand that resolves the contract and checksget_class():wp eval 'var_dump(get_class(evnm(\Eventonomy\Contracts\RecurrenceEngineInterface::class)));'
Related
docs/EXTENDING.md§3 - full Contract reference and container API.docs/ARCHITECTURE.md- the auto-discovery backbone and provider loading.- Recipes Index