aboutsummaryrefslogtreecommitdiffstats
path: root/includes/DomainEvent/DomainEventIngress.php
blob: f069914e8d99420e3cc7f0b324432404d7a9fdbd (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
<?php

namespace MediaWiki\DomainEvent;

use InvalidArgumentException;
use LogicException;

/**
 * Base class for event ingress objects.
 *
 * Event ingress objects implements listener methods for events that a
 * component or extension is interested in. It is responsible for determining
 * which event should trigger which logic in the component and for mapping from
 * the model used by the emitter of the event to the component's own model.
 *
 * DomainEventIngress implements InitializableDomainEventSubscriber so it can be
 * registered with and initialized by a DomainEventSource. Registration is
 * typically done in the form of an object spec for lazy instantiation. For
 * extensions' ingress objects that object spec can be provided in the
 * DomainEventSubscribers section of extension.json.
 *
 * After instantiating a subscriber (typically a subclass of DomainEventIngress),
 * the event source will call initSubscriber() to initialize the subscriber and
 * then registerListeners() to allow the subscriber to register listeners for
 * the events it is interested in.
 *
 * This class provides a default implementation of registerListeners() that will
 * attempt to find listener methods for the events the ingress object is
 * interested in. Listener methods must have a name based on the event type,
 * following the pattern "handle{$eventType}Event".
 *
 * The set of events the ingress objects is interested in must be provided as
 * part of the $options array passed to initSubscriber() when it is called by
 * the event source. This array is simply the same as the object spec used to
 * register the ingress object with the event source. That means that for
 * extensions, the list of events is given as part of the ingress object's spec
 * in extension.json.
 *
 * @since 1.44
 */
abstract class DomainEventIngress implements InitializableDomainEventSubscriber {

	/**
	 * @var string[]
	 */
	private array $eventTypes = [];

	/**
	 * Called by DomainEventDispatcher to provide access to the list of events to
	 * subscribe to and any other relevant information from the extension.json.
	 *
	 * Known keys used in $options:
	 * - 'events': a list of events the ingress object should register listeners
	 *   for (required). The object must implement a listener method for each
	 *   of the events listed here, using the following pattern:
	 *   public function handleSomeEventEvent( SomeEvent $event ).
	 *
	 * @param array $options the object spec describing the subscriber, typically
	 *        from extension.json.
	 */
	public function initSubscriber( array $options ): void {
		if ( !isset( $options['events'] ) ) {
			throw new InvalidArgumentException( '$options must contain the "events" key' );
		}

		if ( $this->eventTypes && $options['events'] != $this->eventTypes ) {
			throw new InvalidArgumentException(
				'A different set of events was provided previously, ' .
				'probably by a call to initEvents().'
			);
		}

		$this->eventTypes = $options['events'];
	}

	/**
	 * Registered any listener methods for the given event.
	 *
	 * @param DomainEventSource $eventSource
	 * @param string $eventType
	 *
	 * @return void
	 */
	protected function registerForEvent(
		DomainEventSource $eventSource,
		string $eventType
	) {
		$found = false;

		// Suffixed are a stand-in for method attributes.
		// NOTE: The 'AfterCommit' suffix is supported for backwards compatibility,
		// but should be removed before the 1.44 release.
		$suffixes = [ '', 'AfterCommit' ];

		foreach ( $suffixes as $sfx ) {
			if ( $this->maybeRegisterListener( $eventSource, $eventType, $sfx ) ) {
				$found = true;
			}
		}

		if ( !$found ) {
			throw new LogicException(
				"No listener methods found for $eventType on " . get_class( $this )
			);
		}
	}

	private function maybeRegisterListener(
		DomainEventSource $eventSource,
		string $eventType,
		string $suffix
	): bool {
		$method = "handle{$eventType}Event{$suffix}";
		if ( !method_exists( $this, $method ) ) {
			return false;
		}

		$eventSource->registerListener(
			$eventType,
			[ $this, $method ],
			$this->getListenerOptions( $eventType, $suffix )
		);
		return true;
	}

	/**
	 * Placeholder method for allowing subclasses to define listener options.
	 * The default implementation could make use of method attributes in the future,
	 * e.g. to determine a listener's priority.
	 *
	 * @unstable for now, intended to become stable to override once the signature is clear.
	 */
	protected function getListenerOptions(
		string $eventType,
		string $suffix
	): array {
		// We may use the options array in the future to communicates things
		// like listener priority, error handling, etc.
		return [];
	}

	/**
	 * This default implementation of registerListeners() will automatically
	 * register a listener method for each event passed to initEvents() or
	 * initSubscriber(). The methods have to start with "handler" followed
	 * by the name of the event followed by "Event".
	 *
	 * @stable to override
	 */
	public function registerListeners( DomainEventSource $eventSource ): void {
		// TODO: look at static::EVENTS too?

		if ( !$this->eventTypes ) {
			throw new LogicException(
				'Subclassed of DomainEventIngress must either override ' .
				'registerListeners or provide a list of event types via ' .
				'initSubscriber() or initEvents().'
			);
		}

		foreach ( $this->eventTypes as $type ) {
			$this->registerForEvent( $eventSource, $type );
		}
	}

}

/** @deprecated temporary alias, remove before 1.44 release (T389033) */
class_alias( DomainEventIngress::class, 'MediaWiki\DomainEvent\EventSubscriberBase' );
/** @deprecated temporary alias, remove before 1.44 release (T389033) */
class_alias( DomainEventIngress::class, 'MediaWiki\DomainEvent\EventIngressBase' );