Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
ServiceOptions
100.00% covered (success)
100.00%
32 / 32
100.00% covered (success)
100.00%
3 / 3
17
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
8
 assertRequiredOptions
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 get
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace MediaWiki\Config;
4
5use InvalidArgumentException;
6use Wikimedia\Assert\Assert;
7
8/**
9 * A class for passing options to services. It can be constructed from a Config, and in practice
10 * most options will be taken from site configuration, but they don't have to be. The options passed
11 * are copied and will not reflect subsequent updates to site configuration (assuming they're not
12 * objects).
13 *
14 * Services that take this type as a parameter to their constructor should specify a list of the
15 * keys they expect to receive in an array. The convention is to make it a public const called
16 * CONSTRUCTOR_OPTIONS. In the constructor, they should call assertRequiredOptions() to make sure
17 * that they weren't passed too few or too many options. This way it's clear what each class
18 * depends on, and that it's getting passed the correct set of options. (This means there are no
19 * optional options. This makes sense for services, since they shouldn't be constructed by
20 * outside code.)
21 *
22 * @newable since 1.36
23 *
24 * @since 1.34
25 */
26class ServiceOptions {
27    private $keys;
28    private $options = [];
29
30    /**
31     * @stable to call since 1.36
32     *
33     * @param string[] $keys Which keys to extract from $sources
34     * @param Config|ServiceOptions|array ...$sources Each source is either a Config object or an array. If the
35     *  same key is present in two sources, the first one takes precedence. Keys that are not in
36     *  $keys are ignored.
37     * @throws InvalidArgumentException if one of $keys is not found in any of $sources
38     */
39    public function __construct( array $keys, ...$sources ) {
40        $this->keys = $keys;
41        foreach ( $keys as $key ) {
42            foreach ( $sources as $source ) {
43                if ( $source instanceof Config ) {
44                    if ( $source->has( $key ) ) {
45                        $this->options[$key] = $source->get( $key );
46                        continue 2;
47                    }
48                } elseif ( $source instanceof ServiceOptions ) {
49                    if ( array_key_exists( $key, $source->options ) ) {
50                        $this->options[$key] = $source->get( $key );
51                        continue 2;
52                    }
53                } else {
54                    if ( array_key_exists( $key, $source ) ) {
55                        $this->options[$key] = $source[$key];
56                        continue 2;
57                    }
58                }
59            }
60            throw new InvalidArgumentException( "Key \"$key\" not found in input sources" );
61        }
62    }
63
64    /**
65     * Assert that the list of options provided in this instance exactly match $expectedKeys,
66     * without regard for order.
67     *
68     * @param string[] $expectedKeys
69     */
70    public function assertRequiredOptions( array $expectedKeys ) {
71        if ( $this->keys !== $expectedKeys ) {
72            $extraKeys = array_diff( $this->keys, $expectedKeys );
73            $missingKeys = array_diff( $expectedKeys, $this->keys );
74            Assert::precondition( !$extraKeys && !$missingKeys,
75                (
76                $extraKeys
77                    ? 'Unsupported options passed: ' . implode( ', ', $extraKeys ) . '!'
78                    : ''
79                ) . ( $extraKeys && $missingKeys ? ' ' : '' ) . (
80                $missingKeys
81                    ? 'Required options missing: ' . implode( ', ', $missingKeys ) . '!'
82                    : ''
83                )
84            );
85        }
86    }
87
88    /**
89     * @param string $key
90     * @return mixed
91     */
92    public function get( $key ) {
93        if ( !array_key_exists( $key, $this->options ) ) {
94            throw new InvalidArgumentException( "Unrecognized option \"$key\"" );
95        }
96        return $this->options[$key];
97    }
98}