Implicitly marking parameter $... as nullable is deprecated in php8.4, the explicit nullable type must be used instead Created with autofix from Ide15839e98a6229c22584d1c1c88c690982e1d7a Break one long line in SpecialPage.php Bug: T376276 Change-Id: I807257b2ba1ab2744ab74d9572c9c3d3ac2a968e
184 lines
5.7 KiB
PHP
184 lines
5.7 KiB
PHP
<?php
|
|
|
|
use MediaWiki\MediaWikiServices;
|
|
use Psr\Log\LoggerAwareInterface;
|
|
use Psr\Log\LoggerInterface;
|
|
use Psr\Log\NullLogger;
|
|
|
|
/**
|
|
* @see ExternalStoreAccess
|
|
* @internal Use the ExternalStoreAccess service instead.
|
|
* @since 1.31
|
|
* @ingroup ExternalStorage
|
|
*/
|
|
class ExternalStoreFactory implements LoggerAwareInterface {
|
|
/** @var string[] List of storage access protocols */
|
|
private $protocols;
|
|
/** @var string[] List of base storage URLs that define locations for writes */
|
|
private $writeBaseUrls;
|
|
/** @var string Default database domain to store content under */
|
|
private $localDomainId;
|
|
/** @var LoggerInterface */
|
|
private $logger;
|
|
/** @var ExternalStoreMedium[] */
|
|
private $stores = [];
|
|
|
|
/**
|
|
* @param string[] $externalStores See $wgExternalStores
|
|
* @param string[] $defaultStores See $wgDefaultExternalStore
|
|
* @param string $localDomainId Local database/wiki ID
|
|
* @param LoggerInterface|null $logger
|
|
*/
|
|
public function __construct(
|
|
array $externalStores,
|
|
array $defaultStores,
|
|
string $localDomainId,
|
|
?LoggerInterface $logger = null
|
|
) {
|
|
$this->protocols = array_map( 'strtolower', $externalStores );
|
|
$this->writeBaseUrls = $defaultStores;
|
|
$this->localDomainId = $localDomainId;
|
|
$this->logger = $logger ?: new NullLogger();
|
|
}
|
|
|
|
public function setLogger( LoggerInterface $logger ) {
|
|
$this->logger = $logger;
|
|
}
|
|
|
|
/**
|
|
* @return string[] List of active store types/protocols (lowercased), e.g. [ "db" ]
|
|
* @since 1.34
|
|
*/
|
|
public function getProtocols() {
|
|
return $this->protocols;
|
|
}
|
|
|
|
/**
|
|
* @return string[] List of default base URLs for writes, e.g. [ "DB://cluster1" ]
|
|
* @since 1.34
|
|
*/
|
|
public function getWriteBaseUrls() {
|
|
return $this->writeBaseUrls;
|
|
}
|
|
|
|
/**
|
|
* Get an external store object of the given type, with the given parameters
|
|
*
|
|
* The 'domain' field in $params will be set to the local DB domain if it is unset
|
|
* or false. A special 'isDomainImplicit' flag is set when this happens, which should
|
|
* only be used to handle legacy DB domain configuration concerns (e.g. T200471).
|
|
*
|
|
* @param string $proto Type of external storage, should be a value in $wgExternalStores
|
|
* @param array $params Map of ExternalStoreMedium::__construct context parameters.
|
|
* @return ExternalStoreMedium The store class or false on error
|
|
* @throws ExternalStoreException When $proto is not recognized
|
|
*/
|
|
public function getStore( $proto, array $params = [] ) {
|
|
$cacheKey = $proto . ':' . json_encode( $params );
|
|
if ( isset( $this->stores[$cacheKey] ) ) {
|
|
return $this->stores[$cacheKey];
|
|
}
|
|
$protoLowercase = strtolower( $proto ); // normalize
|
|
if ( !$this->protocols || !in_array( $protoLowercase, $this->protocols ) ) {
|
|
throw new ExternalStoreException( "Protocol '$proto' is not enabled." );
|
|
}
|
|
|
|
if ( $protoLowercase === 'db' ) {
|
|
$class = 'ExternalStoreDB';
|
|
} else {
|
|
$class = 'ExternalStore' . ucfirst( $proto );
|
|
}
|
|
if ( isset( $params['wiki'] ) ) {
|
|
$params += [ 'domain' => $params['wiki'] ]; // b/c
|
|
}
|
|
if ( !isset( $params['domain'] ) || $params['domain'] === false ) {
|
|
$params['domain'] = $this->localDomainId; // default
|
|
$params['isDomainImplicit'] = true; // b/c for ExternalStoreDB
|
|
}
|
|
// @TODO: ideally, this class should not hardcode what classes need what backend factory
|
|
// objects. For now, inject the factory instances into __construct() for those that do.
|
|
if ( $protoLowercase === 'db' ) {
|
|
$params['lbFactory'] = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
|
|
} elseif ( $protoLowercase === 'mwstore' ) {
|
|
$params['fbGroup'] = MediaWikiServices::getInstance()->getFileBackendGroup();
|
|
}
|
|
$params['logger'] = $this->logger;
|
|
|
|
if ( !class_exists( $class ) ) {
|
|
throw new ExternalStoreException( "Class '$class' is not defined." );
|
|
}
|
|
|
|
// Any custom modules should be added to $wgAutoLoadClasses for on-demand loading
|
|
$this->stores[$cacheKey] = new $class( $params );
|
|
return $this->stores[$cacheKey];
|
|
}
|
|
|
|
/**
|
|
* Get the ExternalStoreMedium for a given URL
|
|
*
|
|
* $url is either of the form:
|
|
* - a) "<proto>://<location>/<path>", for retrieval, or
|
|
* - b) "<proto>://<location>", for storage
|
|
*
|
|
* @param string $url
|
|
* @param array $params Map of ExternalStoreMedium::__construct context parameters
|
|
* @return ExternalStoreMedium
|
|
* @throws ExternalStoreException When the protocol is missing or not recognized
|
|
* @since 1.34
|
|
*/
|
|
public function getStoreForUrl( $url, array $params = [] ) {
|
|
[ $proto, $path ] = self::splitStorageUrl( $url );
|
|
if ( $path == '' ) { // bad URL
|
|
throw new ExternalStoreException( "Invalid URL '$url'" );
|
|
}
|
|
|
|
return $this->getStore( $proto, $params );
|
|
}
|
|
|
|
/**
|
|
* Get the location within the appropriate store for a given a URL
|
|
*
|
|
* @param string $url
|
|
* @return string
|
|
* @throws ExternalStoreException
|
|
* @since 1.34
|
|
*/
|
|
public function getStoreLocationFromUrl( $url ) {
|
|
[ , $location ] = self::splitStorageUrl( $url );
|
|
if ( $location == '' ) { // bad URL
|
|
throw new ExternalStoreException( "Invalid URL '$url'" );
|
|
}
|
|
|
|
return $location;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $urls
|
|
* @return string[][] Map of (protocol => list of URLs)
|
|
* @throws ExternalStoreException
|
|
* @since 1.34
|
|
*/
|
|
public function getUrlsByProtocol( array $urls ) {
|
|
$urlsByProtocol = [];
|
|
foreach ( $urls as $url ) {
|
|
[ $proto, ] = self::splitStorageUrl( $url );
|
|
$urlsByProtocol[$proto][] = $url;
|
|
}
|
|
|
|
return $urlsByProtocol;
|
|
}
|
|
|
|
/**
|
|
* @param string $storeUrl
|
|
* @return string[] (protocol, store location or location-qualified path)
|
|
* @throws ExternalStoreException
|
|
*/
|
|
private static function splitStorageUrl( $storeUrl ) {
|
|
$parts = explode( '://', $storeUrl );
|
|
if ( count( $parts ) != 2 || $parts[0] === '' || $parts[1] === '' ) {
|
|
throw new ExternalStoreException( "Invalid storage URL '$storeUrl'" );
|
|
}
|
|
|
|
return $parts;
|
|
}
|
|
}
|