Completed
Branch master (939199)
by
unknown
39:35
created

includes/PageProps.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Access to properties of a page.
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License along
16
 * with this program; if not, write to the Free Software Foundation, Inc.,
17
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 * http://www.gnu.org/copyleft/gpl.html
19
 *
20
 * @file
21
 */
22
use Wikimedia\ScopedCallback;
23
24
/**
25
 * Gives access to properties of a page.
26
 *
27
 * @since 1.27
28
 *
29
 */
30
class PageProps {
0 ignored issues
show
Since you have declared the constructor as private, maybe you should also declare the class as final.
Loading history...
31
32
	/**
33
	 * @var PageProps
34
	 */
35
	private static $instance;
36
37
	/**
38
	 * Overrides the default instance of this class
39
	 * This is intended for use while testing and will fail if MW_PHPUNIT_TEST is not defined.
40
	 *
41
	 * If this method is used it MUST also be called with null after a test to ensure a new
42
	 * default instance is created next time getInstance is called.
43
	 *
44
	 * @since 1.27
45
	 *
46
	 * @param PageProps|null $store
47
	 *
48
	 * @return ScopedCallback to reset the overridden value
49
	 * @throws MWException
50
	 */
51
	public static function overrideInstance( PageProps $store = null ) {
52
		if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
53
			throw new MWException(
54
				'Cannot override ' . __CLASS__ . 'default instance in operation.'
55
			);
56
		}
57
		$previousValue = self::$instance;
58
		self::$instance = $store;
59
		return new ScopedCallback( function() use ( $previousValue ) {
60
			self::$instance = $previousValue;
61
		} );
62
	}
63
64
	/**
65
	 * @return PageProps
66
	 */
67
	public static function getInstance() {
68
		if ( self::$instance === null ) {
69
			self::$instance = new self();
70
		}
71
		return self::$instance;
72
	}
73
74
	/** Cache parameters */
75
	const CACHE_TTL = 10; // integer; TTL in seconds
76
	const CACHE_SIZE = 100; // integer; max cached pages
77
78
	/** Property cache */
79
	private $cache = null;
80
81
	/**
82
	 * Create a PageProps object
83
	 */
84
	private function __construct() {
85
		$this->cache = new ProcessCacheLRU( self::CACHE_SIZE );
86
	}
87
88
	/**
89
	 * Ensure that cache has at least this size
90
	 * @param int $size
91
	 */
92
	public function ensureCacheSize( $size ) {
93
		if ( $this->cache->getSize() < $size ) {
94
			$this->cache->resize( $size );
95
		}
96
	}
97
98
	/**
99
	 * Given one or more Titles and one or more names of properties,
100
	 * returns an associative array mapping page ID to property value.
101
	 * Pages in the provided set of Titles that do not have a value for
102
	 * the given properties will not appear in the returned array. If a
103
	 * single Title is provided, it does not need to be passed in an array,
104
	 * but an array will always be returned. If a single property name is
105
	 * provided, it does not need to be passed in an array. In that case,
106
	 * an associative array mapping page ID to property value will be
107
	 * returned; otherwise, an associative array mapping page ID to
108
	 * an associative array mapping property name to property value will be
109
	 * returned. An empty array will be returned if no matching properties
110
	 * were found.
111
	 *
112
	 * @param Title[]|Title $titles
113
	 * @param string[]|string $propertyNames
114
	 * @return array associative array mapping page ID to property value
115
	 */
116
	public function getProperties( $titles, $propertyNames ) {
117
		if ( is_array( $propertyNames ) ) {
118
			$gotArray = true;
119
		} else {
120
			$propertyNames = [ $propertyNames ];
121
			$gotArray = false;
122
		}
123
124
		$values = [];
125
		$goodIDs = $this->getGoodIDs( $titles );
126
		$queryIDs = [];
127
		foreach ( $goodIDs as $pageID ) {
128
			foreach ( $propertyNames as $propertyName ) {
129
				$propertyValue = $this->getCachedProperty( $pageID, $propertyName );
130
				if ( $propertyValue === false ) {
131
					$queryIDs[] = $pageID;
132
					break;
133
				} else {
134
					if ( $gotArray ) {
135
						$values[$pageID][$propertyName] = $propertyValue;
136
					} else {
137
						$values[$pageID] = $propertyValue;
138
					}
139
				}
140
			}
141
		}
142
143
		if ( $queryIDs ) {
144
			$dbr = wfGetDB( DB_REPLICA );
145
			$result = $dbr->select(
146
				'page_props',
147
				[
148
					'pp_page',
149
					'pp_propname',
150
					'pp_value'
151
				],
152
				[
153
					'pp_page' => $queryIDs,
154
					'pp_propname' => $propertyNames
155
				],
156
				__METHOD__
157
			);
158
159
			foreach ( $result as $row ) {
160
				$pageID = $row->pp_page;
161
				$propertyName = $row->pp_propname;
162
				$propertyValue = $row->pp_value;
163
				$this->cacheProperty( $pageID, $propertyName, $propertyValue );
164
				if ( $gotArray ) {
165
					$values[$pageID][$propertyName] = $propertyValue;
166
				} else {
167
					$values[$pageID] = $propertyValue;
168
				}
169
			}
170
		}
171
172
		return $values;
173
	}
174
175
	/**
176
	 * Get all page property values.
177
	 * Given one or more Titles, returns an associative array mapping page
178
	 * ID to an associative array mapping property names to property
179
	 * values. Pages in the provided set of Titles that do not have any
180
	 * properties will not appear in the returned array. If a single Title
181
	 * is provided, it does not need to be passed in an array, but an array
182
	 * will always be returned. An empty array will be returned if no
183
	 * matching properties were found.
184
	 *
185
	 * @param Title[]|Title $titles
186
	 * @return array associative array mapping page ID to property value array
187
	 */
188
	public function getAllProperties( $titles ) {
189
		$values = [];
190
		$goodIDs = $this->getGoodIDs( $titles );
191
		$queryIDs = [];
192
		foreach ( $goodIDs as $pageID ) {
193
			$pageProperties = $this->getCachedProperties( $pageID );
194
			if ( $pageProperties === false ) {
195
				$queryIDs[] = $pageID;
196
			} else {
197
				$values[$pageID] = $pageProperties;
198
			}
199
		}
200
201
		if ( $queryIDs != [] ) {
202
			$dbr = wfGetDB( DB_REPLICA );
203
			$result = $dbr->select(
204
				'page_props',
205
				[
206
					'pp_page',
207
					'pp_propname',
208
					'pp_value'
209
				],
210
				[
211
					'pp_page' => $queryIDs,
212
				],
213
				__METHOD__
214
			);
215
216
			$currentPageID = 0;
217
			$pageProperties = [];
218
			foreach ( $result as $row ) {
219
				$pageID = $row->pp_page;
220
				if ( $currentPageID != $pageID ) {
221
					if ( $pageProperties != [] ) {
222
						$this->cacheProperties( $currentPageID, $pageProperties );
223
						$values[$currentPageID] = $pageProperties;
224
					}
225
					$currentPageID = $pageID;
226
					$pageProperties = [];
227
				}
228
				$pageProperties[$row->pp_propname] = $row->pp_value;
229
			}
230
			if ( $pageProperties != [] ) {
231
				$this->cacheProperties( $pageID, $pageProperties );
0 ignored issues
show
The variable $pageID does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
232
				$values[$pageID] = $pageProperties;
233
			}
234
		}
235
236
		return $values;
237
	}
238
239
	/**
240
	 * @param Title[]|Title $titles
241
	 * @return array array of good page IDs
242
	 */
243
	private function getGoodIDs( $titles ) {
244
		$result = [];
245
		if ( is_array( $titles ) ) {
246
			foreach ( $titles as $title ) {
247
				$pageID = $title->getArticleID();
248
				if ( $pageID > 0 ) {
249
					$result[] = $pageID;
250
				}
251
			}
252
		} else {
253
			$pageID = $titles->getArticleID();
254
			if ( $pageID > 0 ) {
255
				$result[] = $pageID;
256
			}
257
		}
258
		return $result;
259
	}
260
261
	/**
262
	 * Get a property from the cache.
263
	 *
264
	 * @param int $pageID page ID of page being queried
265
	 * @param string $propertyName name of property being queried
266
	 * @return string|bool property value array or false if not found
267
	 */
268
	private function getCachedProperty( $pageID, $propertyName ) {
269
		if ( $this->cache->has( $pageID, $propertyName, self::CACHE_TTL ) ) {
270
			return $this->cache->get( $pageID, $propertyName );
271
		}
272
		if ( $this->cache->has( 0, $pageID, self::CACHE_TTL ) ) {
273
			$pageProperties = $this->cache->get( 0, $pageID );
274
			if ( isset( $pageProperties[$propertyName] ) ) {
275
				return $pageProperties[$propertyName];
276
			}
277
		}
278
		return false;
279
	}
280
281
	/**
282
	 * Get properties from the cache.
283
	 *
284
	 * @param int $pageID page ID of page being queried
285
	 * @return string|bool property value array or false if not found
286
	 */
287
	private function getCachedProperties( $pageID ) {
288
		if ( $this->cache->has( 0, $pageID, self::CACHE_TTL ) ) {
289
			return $this->cache->get( 0, $pageID );
290
		}
291
		return false;
292
	}
293
294
	/**
295
	 * Save a property to the cache.
296
	 *
297
	 * @param int $pageID page ID of page being cached
298
	 * @param string $propertyName name of property being cached
299
	 * @param mixed $propertyValue value of property
300
	 */
301
	private function cacheProperty( $pageID, $propertyName, $propertyValue ) {
302
		$this->cache->set( $pageID, $propertyName, $propertyValue );
303
	}
304
305
	/**
306
	 * Save properties to the cache.
307
	 *
308
	 * @param int $pageID page ID of page being cached
309
	 * @param string[] $pageProperties associative array of page properties to be cached
310
	 */
311
	private function cacheProperties( $pageID, $pageProperties ) {
312
		$this->cache->clear( $pageID );
313
		$this->cache->set( 0, $pageID, $pageProperties );
314
	}
315
}
316