Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
EventBusEventSubmitter
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
3 / 3
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 submit
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
4
 prepareEvent
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Extension\EventLogging\EventSubmitter;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Deferred\DeferredUpdates;
7use MediaWiki\Extension\EventBus\EventBus;
8use MediaWiki\MediaWikiServices;
9use Psr\Log\LoggerInterface;
10
11/**
12 * Submits events to an instance of EventGate via EventBus.
13 *
14 * @see https://wikitech.wikimedia.org/wiki/Event_Platform/EventGate
15 * @see https://www.mediawiki.org/wiki/Extension:EventBus
16 */
17class EventBusEventSubmitter implements EventSubmitter {
18
19    /** @var LoggerInterface */
20    private $logger;
21
22    /** @var string */
23    private $domain;
24
25    public function __construct( LoggerInterface $logger, Config $config ) {
26        $this->logger = $logger;
27        $this->domain = $config->get( 'ServerName' );
28    }
29
30    /**
31     * @inheritDoc
32     */
33    public function submit( string $streamName, array $event ): void {
34        if ( !isset( $event['$schema'] ) ) {
35            $this->logger->warning(
36                'Event data is missing required field "$schema"',
37                [ 'event' => $event ]
38            );
39
40            return;
41        }
42
43        $event = $this->prepareEvent( $streamName, $event );
44        $logger = $this->logger;
45
46        DeferredUpdates::addCallableUpdate( static function () use ( $streamName, $event, $logger ) {
47            $services = MediaWikiServices::getInstance();
48            $streamConfigs = $services->getService( 'EventLogging.StreamConfigs' );
49
50            if ( $streamConfigs !== false && !array_key_exists( $streamName, $streamConfigs ) ) {
51                $logger->warning(
52                    'Event submitted for unregistered stream name "{streamName}"',
53                    [ 'streamName' => $streamName ]
54                );
55                return;
56            }
57
58            // @phan-suppress-next-line PhanUndeclaredClassMethod
59            EventBus::getInstanceForStream( $streamName )->send( [ $event ] );
60        } );
61    }
62
63    /**
64     * Prepares the event for submission by:
65     *
66     * 1. Setting the `meta.stream` required property;
67     * 2. Setting the `dt` required property, if it is not set; and
68     * 3. Setting the `http.request_headers.user_agent` and `meta.domain` properties, if they are
69     *    not set
70     *
71     * Note well that we test for the `meta.$schema` required property being set in
72     * {@link EventBusEventSubmitter::submit()} above.
73     *
74     * @see https://wikitech.wikimedia.org/wiki/Event_Platform/Schemas/Guidelines#Required_fields
75     *
76     * @param string $streamName
77     * @param array $event
78     * @return array The prepared event
79     */
80    private function prepareEvent( string $streamName, array $event ): array {
81        $defaults = [
82            'http' => [
83                'request_headers' => [
84                    'user-agent' => $_SERVER[ 'HTTP_USER_AGENT' ] ?? ''
85                ]
86            ],
87            'meta' => [
88                'domain' => $this->domain,
89            ],
90            'dt' => wfTimestamp( TS_ISO_8601 ),
91        ];
92
93        $requiredData = [
94            'meta' => [
95                'stream' => $streamName,
96            ],
97        ];
98
99        $event = array_replace_recursive(
100            $defaults,
101            $event,
102            $requiredData
103        );
104
105        //
106        // If this is a migrated legacy event, client_dt will have been set already by
107        // EventLogging::encapsulate, and the dt field should be left unset so that it can be set
108        // to the intake time by EventGate. If dt was set by a caller, we unset it here.
109        //
110        // If client_dt is absent, this schema is native to the Event Platform, and dt represents
111        // the client-side event time. We set it here, overwriting any caller-provided value to
112        // ensure consistency.
113        //
114        // https://phabricator.wikimedia.org/T277253
115        // https://phabricator.wikimedia.org/T277330
116        //
117        if ( isset( $event['client_dt'] ) ) {
118            unset( $event['dt'] );
119        }
120
121        return $event;
122    }
123}