Complex classes like ApiOpenSearch often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use ApiOpenSearch, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
32 | class ApiOpenSearch extends ApiBase { |
||
33 | use SearchApi; |
||
34 | |||
35 | private $format = null; |
||
36 | private $fm = null; |
||
37 | |||
38 | /** @var array list of api allowed params */ |
||
39 | private $allowedParams = null; |
||
40 | |||
41 | /** |
||
42 | * Get the output format |
||
43 | * |
||
44 | * @return string |
||
45 | */ |
||
46 | protected function getFormat() { |
||
66 | |||
67 | public function getCustomPrinter() { |
||
83 | |||
84 | public function execute() { |
||
85 | $params = $this->extractRequestParams(); |
||
86 | $search = $params['search']; |
||
87 | $suggest = $params['suggest']; |
||
88 | $results = []; |
||
89 | if ( !$suggest || $this->getConfig()->get( 'EnableOpenSearchSuggest' ) ) { |
||
90 | // Open search results may be stored for a very long time |
||
91 | $this->getMain()->setCacheMaxAge( $this->getConfig()->get( 'SearchSuggestCacheExpiry' ) ); |
||
92 | $this->getMain()->setCacheMode( 'public' ); |
||
93 | $results = $this->search( $search, $params ); |
||
94 | |||
95 | // Allow hooks to populate extracts and images |
||
96 | Hooks::run( 'ApiOpenSearchSuggest', [ &$results ] ); |
||
97 | |||
98 | // Trim extracts, if necessary |
||
99 | $length = $this->getConfig()->get( 'OpenSearchDescriptionLength' ); |
||
100 | foreach ( $results as &$r ) { |
||
101 | if ( is_string( $r['extract'] ) && !$r['extract trimmed'] ) { |
||
102 | $r['extract'] = self::trimExtract( $r['extract'], $length ); |
||
103 | } |
||
104 | } |
||
105 | } |
||
106 | |||
107 | // Populate result object |
||
108 | $this->populateResult( $search, $results ); |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Perform the search |
||
113 | * @param string $search the search query |
||
114 | * @param array $params api request params |
||
115 | * @return array search results. Keys are integers. |
||
116 | */ |
||
117 | private function search( $search, array $params ) { |
||
118 | $searchEngine = $this->buildSearchEngine( $params ); |
||
119 | $titles = $searchEngine->extractTitles( $searchEngine->completionSearchWithVariants( $search ) ); |
||
120 | $results = []; |
||
121 | |||
122 | if ( !$titles ) { |
||
123 | return $results; |
||
124 | } |
||
125 | |||
126 | // Special pages need unique integer ids in the return list, so we just |
||
127 | // assign them negative numbers because those won't clash with the |
||
128 | // always positive articleIds that non-special pages get. |
||
129 | $nextSpecialPageId = -1; |
||
130 | |||
131 | if ( $params['redirects'] === null ) { |
||
132 | // Backwards compatibility, don't resolve for JSON. |
||
133 | $resolveRedir = $this->getFormat() !== 'json'; |
||
134 | } else { |
||
135 | $resolveRedir = $params['redirects'] === 'resolve'; |
||
136 | } |
||
137 | |||
138 | if ( $resolveRedir ) { |
||
139 | // Query for redirects |
||
140 | $redirects = []; |
||
141 | $lb = new LinkBatch( $titles ); |
||
142 | if ( !$lb->isEmpty() ) { |
||
143 | $db = $this->getDB(); |
||
144 | $res = $db->select( |
||
145 | [ 'page', 'redirect' ], |
||
146 | [ 'page_namespace', 'page_title', 'rd_namespace', 'rd_title' ], |
||
147 | [ |
||
148 | 'rd_from = page_id', |
||
149 | 'rd_interwiki IS NULL OR rd_interwiki = ' . $db->addQuotes( '' ), |
||
150 | $lb->constructSet( 'page', $db ), |
||
151 | ], |
||
152 | __METHOD__ |
||
153 | ); |
||
154 | foreach ( $res as $row ) { |
||
|
|||
155 | $redirects[$row->page_namespace][$row->page_title] = |
||
156 | [ $row->rd_namespace, $row->rd_title ]; |
||
157 | } |
||
158 | } |
||
159 | |||
160 | // Bypass any redirects |
||
161 | $seen = []; |
||
162 | foreach ( $titles as $title ) { |
||
163 | $ns = $title->getNamespace(); |
||
164 | $dbkey = $title->getDBkey(); |
||
165 | $from = null; |
||
166 | if ( isset( $redirects[$ns][$dbkey] ) ) { |
||
167 | list( $ns, $dbkey ) = $redirects[$ns][$dbkey]; |
||
168 | $from = $title; |
||
169 | $title = Title::makeTitle( $ns, $dbkey ); |
||
170 | } |
||
171 | if ( !isset( $seen[$ns][$dbkey] ) ) { |
||
172 | $seen[$ns][$dbkey] = true; |
||
173 | $resultId = $title->getArticleID(); |
||
174 | if ( $resultId === 0 ) { |
||
175 | $resultId = $nextSpecialPageId; |
||
176 | $nextSpecialPageId -= 1; |
||
177 | } |
||
178 | $results[$resultId] = [ |
||
179 | 'title' => $title, |
||
180 | 'redirect from' => $from, |
||
181 | 'extract' => false, |
||
182 | 'extract trimmed' => false, |
||
183 | 'image' => false, |
||
184 | 'url' => wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ), |
||
185 | ]; |
||
186 | } |
||
187 | } |
||
188 | } else { |
||
189 | foreach ( $titles as $title ) { |
||
190 | $resultId = $title->getArticleID(); |
||
191 | if ( $resultId === 0 ) { |
||
192 | $resultId = $nextSpecialPageId; |
||
193 | $nextSpecialPageId -= 1; |
||
194 | } |
||
195 | $results[$resultId] = [ |
||
196 | 'title' => $title, |
||
197 | 'redirect from' => null, |
||
198 | 'extract' => false, |
||
199 | 'extract trimmed' => false, |
||
200 | 'image' => false, |
||
201 | 'url' => wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ), |
||
202 | ]; |
||
203 | } |
||
204 | } |
||
205 | |||
206 | return $results; |
||
207 | } |
||
208 | |||
209 | /** |
||
210 | * @param string $search |
||
211 | * @param array &$results |
||
212 | */ |
||
213 | protected function populateResult( $search, &$results ) { |
||
270 | |||
271 | public function getAllowedParams() { |
||
272 | if ( $this->allowedParams !== null ) { |
||
273 | return $this->allowedParams; |
||
274 | } |
||
275 | $this->allowedParams = $this->buildCommonApiParams( false ) + [ |
||
276 | 'suggest' => false, |
||
277 | 'redirects' => [ |
||
278 | ApiBase::PARAM_TYPE => [ 'return', 'resolve' ], |
||
279 | ], |
||
280 | 'format' => [ |
||
281 | ApiBase::PARAM_DFLT => 'json', |
||
282 | ApiBase::PARAM_TYPE => [ 'json', 'jsonfm', 'xml', 'xmlfm' ], |
||
283 | ], |
||
284 | 'warningsaserror' => false, |
||
285 | ]; |
||
286 | |||
287 | // Use open search specific default limit |
||
288 | $this->allowedParams['limit'][ApiBase::PARAM_DFLT] = $this->getConfig()->get( |
||
289 | 'OpenSearchDefaultLimit' |
||
290 | ); |
||
291 | |||
292 | return $this->allowedParams; |
||
293 | } |
||
294 | |||
295 | public function getSearchProfileParams() { |
||
296 | return [ |
||
297 | 'profile' => [ |
||
298 | 'profile-type' => SearchEngine::COMPLETION_PROFILE_TYPE, |
||
299 | 'help-message' => 'apihelp-query+prefixsearch-param-profile' |
||
300 | ], |
||
301 | ]; |
||
302 | } |
||
303 | |||
304 | protected function getExamplesMessages() { |
||
305 | return [ |
||
306 | 'action=opensearch&search=Te' |
||
307 | => 'apihelp-opensearch-example-te', |
||
308 | ]; |
||
309 | } |
||
310 | |||
311 | public function getHelpUrls() { |
||
312 | return 'https://www.mediawiki.org/wiki/API:Opensearch'; |
||
313 | } |
||
314 | |||
315 | /** |
||
316 | * Trim an extract to a sensible length. |
||
317 | * |
||
318 | * Adapted from Extension:OpenSearchXml, which adapted it from |
||
319 | * Extension:ActiveAbstract. |
||
320 | * |
||
321 | * @param string $text |
||
322 | * @param int $length Target length; actual result will continue to the end of a sentence. |
||
323 | * @return string |
||
324 | */ |
||
325 | public static function trimExtract( $text, $length ) { |
||
349 | |||
350 | /** |
||
351 | * Fetch the template for a type. |
||
352 | * |
||
353 | * @param string $type MIME type |
||
354 | * @return string |
||
355 | * @throws MWException |
||
356 | */ |
||
357 | public static function getOpenSearchTemplate( $type ) { |
||
383 | } |
||
384 | |||
385 | class ApiOpenSearchFormatJson extends ApiFormatJson { |
||
386 | private $warningsAsError = false; |
||
387 | |||
419 |
There are different options of fixing this problem.
If you want to be on the safe side, you can add an additional type-check:
If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:
Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.