Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
WikiPageConfig
0.00% covered (danger)
0.00%
0 / 35
0.00% covered (danger)
0.00%
0 / 7
210
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getConfigTitle
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
20
 getConfigData
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
20
 get
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getWithFlags
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 has
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 hasWithFlags
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace GrowthExperiments\Config;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Config\ConfigException;
7use MediaWiki\Title\Title;
8use MediaWiki\Title\TitleFactory;
9use Psr\Log\LoggerInterface;
10use StatusValue;
11
12class WikiPageConfig implements Config {
13
14    private LoggerInterface $logger;
15    private TitleFactory $titleFactory;
16    private ?WikiPageConfigLoader $configLoader;
17    private ?string $rawConfigTitle;
18    private ?Title $configTitle = null;
19    /**
20     * @var bool Hack to disable DB access in non-database tests.
21     */
22    private bool $isTestWithStorageDisabled;
23
24    /**
25     * @param LoggerInterface $logger
26     * @param TitleFactory $titleFactory
27     * @param WikiPageConfigLoader $configLoader
28     * @param string $rawConfigTitle
29     * @param bool $isTestWithStorageDisabled
30     */
31    public function __construct(
32        LoggerInterface $logger,
33        TitleFactory $titleFactory,
34        WikiPageConfigLoader $configLoader,
35        string $rawConfigTitle,
36        bool $isTestWithStorageDisabled
37    ) {
38        $this->logger = $logger;
39        $this->titleFactory = $titleFactory;
40        $this->configLoader = $configLoader;
41        $this->rawConfigTitle = $rawConfigTitle;
42        $this->isTestWithStorageDisabled = $isTestWithStorageDisabled;
43    }
44
45    /**
46     * Helper to late-construct Title
47     *
48     * Config is initialized pretty early. This allows us to delay construction of
49     * Title (which may talk to the DB) until whenever config is first fetched,
50     * which should be much later, and probably after init sequence finished.
51     *
52     * @throws ConfigException
53     * @return Title
54     */
55    private function getConfigTitle(): Title {
56        if ( $this->configTitle == null ) {
57            $configTitle = $this->titleFactory->newFromText( $this->rawConfigTitle );
58
59            if (
60                $configTitle === null ||
61                !$configTitle->isSiteJsonConfigPage()
62            ) {
63                throw new ConfigException( 'Invalid GEWikiConfigPageTitle' );
64            }
65
66            $this->configTitle = $configTitle;
67        }
68
69        return $this->configTitle;
70    }
71
72    /**
73     * Helper function to fetch config data from wiki page
74     *
75     * This may sound expensive, but WikiPageConfigLoader is supposed
76     * to take care about caching.
77     *
78     * @param int $flags bit field, see IDBAccessObject::READ_XXX
79     *
80     * @throws ConfigException on an error
81     * @return array
82     */
83    private function getConfigData( int $flags = 0 ): array {
84        if ( $this->isTestWithStorageDisabled ) {
85            return [];
86        }
87        if ( !$this->getConfigTitle()->exists() ) {
88            // configLoader throws an exception for no-page case
89            return [];
90        }
91        $res = $this->configLoader->load( $this->getConfigTitle(), $flags );
92        if ( $res instanceof StatusValue ) {
93            // Loading config failed. This can happen in case of both a software error and
94            // an error made by an administrator (setting the JSON blob manually to something
95            // badly malformed, ie. set an array when a bool is expected). Log the error, and
96            // pretend there is nothing in the JSON blob.
97
98            $this->logger->error(
99                __METHOD__ . ' failed to load config from wiki: {error}',
100                [
101                    'error' => (string)$res,
102                    'impact' => 'Config stored in MediaWiki:GrowthExperimentsConfig.json ' .
103                        'is ignored, using sane fallbacks instead'
104                ]
105            );
106
107            // NOTE: This code branch SHOULD NOT throw a ConfigException. Throwing an exception
108            // would make _both_ get() and has() throw an exception, while returning an empty
109            // array means has() finishes nicely (with a false), while get still throws an
110            // exception (as calling get with has() returning false is unexpected). That behavior
111            // is necessary for GrowthExperimentsMultiConfig (which is a wrapper around
112            // MultiConfig) to work. When has() returns false, MultiConfig consults the fallback(s),
113            // but with an exception thrown, it stops processing, and the exception propagates up to
114            // the user.
115            return [];
116        }
117        return $res;
118    }
119
120    /**
121     * @inheritDoc
122     */
123    public function get( $name ) {
124        return $this->getWithFlags( $name );
125    }
126
127    /**
128     * @param string $name
129     * @param int $flags bit field, see IDBAccessObject::READ_XXX
130     * @return mixed Config value
131     */
132    public function getWithFlags( $name, int $flags = 0 ) {
133        $configData = $this->getConfigData( $flags );
134        if ( !array_key_exists( $name, $configData ) ) {
135            throw new ConfigException( 'Config key was not found in WikiPageConfig' );
136        }
137
138        return $configData[ $name ];
139    }
140
141    /**
142     * @inheritDoc
143     */
144    public function has( $name ) {
145        return $this->hasWithFlags( $name );
146    }
147
148    /**
149     * @param string $name
150     * @param int $flags bit field, see IDBAccessObject::READ_XXX
151     * @return bool
152     */
153    public function hasWithFlags( $name, int $flags = 0 ) {
154        return array_key_exists( $name, $this->getConfigData( $flags ) );
155    }
156}