3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
21 namespace MediaWiki\Site
;
24 use InvalidArgumentException
;
27 * Array-like collection of Site objects.
29 * This uses ArrayObject to intercept additions and deletions for purposes
30 * such as additional indexing, and to enforce that values are restricted
31 * to Site objects only.
35 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
37 class SiteList
extends ArrayObject
{
39 * @see SiteList::getNewOffset()
43 protected $indexOffset = 0;
46 * Internal site identifiers pointing to their sites offset value.
50 * @var array Maps int identifiers to local ArrayObject keys
52 protected $byInternalId = [];
55 * Global site identifiers pointing to their sites offset value.
59 * @var array Maps string identifiers to local ArrayObject keys
61 protected $byGlobalId = [];
64 * Navigational site identifiers alias inter-language prefixes
65 * pointing to their sites offset value.
69 * @var array Maps string identifiers to local ArrayObject keys
71 protected $byNavigationId = [];
74 * @see Overrides ArrayObject::__construct https://www.php.net/manual/en/arrayobject.construct.php
76 * @param null|array $input
78 * @param string $iterator_class
80 public function __construct( $input = null, $flags = 0, $iterator_class = 'ArrayIterator' ) {
81 parent
::__construct( [], $flags, $iterator_class );
83 if ( $input !== null ) {
84 foreach ( $input as $offset => $value ) {
85 $this->offsetSet( $offset, $value );
91 * @see Overrides ArrayObject::append
95 public function append( $value ): void
{
96 $this->setElement( null, $value );
101 * @see Overrides ArrayObject::offsetSet()
102 * @param mixed $index
103 * @param mixed $value
105 public function offsetSet( $index, $value ): void
{
106 $this->setElement( $index, $value );
110 * Returns if the provided value has the same type as the elements
111 * that can be added to this ArrayObject.
114 * @param mixed $value
117 protected function hasValidType( $value ) {
118 $class = $this->getObjectType();
119 return $value instanceof $class;
123 * The class or interface type that array elements must match.
128 public function getObjectType() {
133 * Find a new offset for when appending an element.
138 protected function getNewOffset() {
139 while ( $this->offsetExists( $this->indexOffset
) ) {
140 $this->indexOffset++
;
143 return $this->indexOffset
;
147 * Actually set the element and enforce type checking and offset resolving.
150 * @param int|string|null $index
153 protected function setElement( $index, $site ) {
154 if ( !$this->hasValidType( $site ) ) {
155 throw new InvalidArgumentException(
156 'Can only add ' . $this->getObjectType() . ' implementing objects to ' . static::class . '.'
160 $index ??
= $this->getNewOffset();
162 if ( $this->hasSite( $site->getGlobalId() ) ) {
163 $this->removeSite( $site->getGlobalId() );
166 $this->byGlobalId
[$site->getGlobalId()] = $index;
167 $this->byInternalId
[$site->getInternalId()] = $index;
169 $ids = $site->getNavigationIds();
170 foreach ( $ids as $navId ) {
171 $this->byNavigationId
[$navId] = $index;
174 parent
::offsetSet( $index, $site );
178 * @see ArrayObject::offsetUnset()
182 * @param mixed $index
184 public function offsetUnset( $index ): void
{
185 if ( $this->offsetExists( $index ) ) {
189 $site = $this->offsetGet( $index );
191 unset( $this->byGlobalId
[$site->getGlobalId()] );
192 unset( $this->byInternalId
[$site->getInternalId()] );
194 $ids = $site->getNavigationIds();
195 foreach ( $ids as $navId ) {
196 unset( $this->byNavigationId
[$navId] );
200 parent
::offsetUnset( $index );
204 * Returns all the global site identifiers.
205 * Optionally only those belonging to the specified group.
211 public function getGlobalIdentifiers() {
212 return array_keys( $this->byGlobalId
);
216 * Returns if the list contains the site with the provided global site identifier.
218 * @param string $globalSiteId
222 public function hasSite( $globalSiteId ) {
223 return array_key_exists( $globalSiteId, $this->byGlobalId
);
227 * Returns the Site with the provided global site identifier.
228 * The site needs to exist, so if not sure, call hasGlobalId first.
232 * @param string $globalSiteId
236 public function getSite( $globalSiteId ) {
237 return $this->offsetGet( $this->byGlobalId
[$globalSiteId] );
241 * Removes the site with the specified global site identifier.
242 * The site needs to exist, so if not sure, call hasGlobalId first.
246 * @param string $globalSiteId
248 public function removeSite( $globalSiteId ) {
249 $this->offsetUnset( $this->byGlobalId
[$globalSiteId] );
253 * Whether the list contains no sites.
258 public function isEmpty() {
259 return $this->byGlobalId
=== [];
263 * Returns if the list contains the site with the provided site id.
269 public function hasInternalId( $id ) {
270 return array_key_exists( $id, $this->byInternalId
);
274 * Returns the Site with the provided site id.
275 * The site needs to exist, so if not sure, call has first.
283 public function getSiteByInternalId( $id ) {
284 return $this->offsetGet( $this->byInternalId
[$id] );
288 * Removes the site with the specified site id.
289 * The site needs to exist, so if not sure, call has first.
295 public function removeSiteByInternalId( $id ) {
296 $this->offsetUnset( $this->byInternalId
[$id] );
300 * Returns if the list contains the site with the provided navigational site id.
306 public function hasNavigationId( $id ) {
307 return array_key_exists( $id, $this->byNavigationId
);
311 * Returns the Site with the provided navigational site id.
312 * The site needs to exist, so if not sure, call has first.
320 public function getSiteByNavigationId( $id ) {
321 return $this->offsetGet( $this->byNavigationId
[$id] );
325 * Removes the site with the specified navigational site id.
326 * The site needs to exist, so if not sure, call has first.
332 public function removeSiteByNavigationId( $id ) {
333 $this->offsetUnset( $this->byNavigationId
[$id] );
337 * Sets a site in the list. If the site was not there,
338 * it will be added. If it was, it will be updated.
344 public function setSite( Site
$site ) {
349 * Returns the sites that are in the provided group.
353 * @param string $groupName
357 public function getGroup( $groupName ) {
363 foreach ( $this as $site ) {
364 if ( $site->getGroup() === $groupName ) {
373 * A version ID that identifies the serialization structure used by __serialize()
374 * and unserialize(). This is useful for constructing cache keys in cases where the cache relies
375 * on serialization for storing the SiteList.
377 * @var string A string uniquely identifying the version of the serialization structure,
378 * not including any sub-structures.
380 private const SERIAL_VERSION_ID
= '2014-03-17';
383 * Returns the version ID that identifies the serialization structure used by
384 * __serialize() and unserialize(), including the structure of any nested structures.
385 * This is useful for constructing cache keys in cases where the cache relies
386 * on serialization for storing the SiteList.
388 * @return string A string uniquely identifying the version of the serialization structure,
389 * including any sub-structures.
391 public static function getSerialVersionId() {
392 return self
::SERIAL_VERSION_ID
. '+Site:' . Site
::SERIAL_VERSION_ID
;
396 * @see Overrides Serializable::serialize
400 public function __serialize(): array {
401 // Data that should go into serialization calls.
403 // NOTE: When changing the structure, either implement unserialize() to handle the
404 // old structure too, or update SERIAL_VERSION_ID to kill any caches.
406 'data' => $this->getArrayCopy(),
407 'index' => $this->indexOffset
,
408 'internalIds' => $this->byInternalId
,
409 'globalIds' => $this->byGlobalId
,
410 'navigationIds' => $this->byNavigationId
,
415 * @see Overrides Serializable::unserialize
417 * @param array $serializationData
419 public function __unserialize( $serializationData ): void
{
420 foreach ( $serializationData['data'] as $offset => $value ) {
421 // Just set the element, bypassing checks and offset resolving,
422 // as these elements have already gone through this.
423 parent
::offsetSet( $offset, $value );
426 $this->indexOffset
= $serializationData['index'];
428 $this->byInternalId
= $serializationData['internalIds'];
429 $this->byGlobalId
= $serializationData['globalIds'];
430 $this->byNavigationId
= $serializationData['navigationIds'];
434 /** @deprecated class alias since 1.42 */
435 class_alias( SiteList
::class, 'SiteList' );