Revert "Parser: Add Title type hints"
[lhc/web/wiklou.git] / includes / externalstore / ExternalStoreFactory.php
index 940fb2e..9998640 100644 (file)
  * @defgroup ExternalStorage ExternalStorage
  */
 
+use MediaWiki\MediaWikiServices;
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
+use Wikimedia\Assert\Assert;
+
 /**
  * @ingroup ExternalStorage
  */
-class ExternalStoreFactory {
+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 array
+        * @param string[] $externalStores See $wgExternalStores
+        * @param string[] $defaultStores See $wgDefaultExternalStore
+        * @param string $localDomainId Local database/wiki ID
+        * @param LoggerInterface|null $logger
         */
-       private $externalStores;
+       public function __construct(
+               array $externalStores,
+               array $defaultStores,
+               $localDomainId,
+               LoggerInterface $logger = null
+       ) {
+               Assert::parameterType( 'string', $localDomainId, '$localDomainId' );
+
+               $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;
+       }
 
        /**
-        * @param array $externalStores See $wgExternalStores
+        * @return string[] List of active store types/protocols (lowercased), e.g. [ "db" ]
+        * @since 1.34
         */
-       public function __construct( array $externalStores ) {
-               $this->externalStores = array_map( 'strtolower', $externalStores );
+       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 Associative array of ExternalStoreMedium parameters
-        * @return ExternalStoreMedium|bool The store class or false on error
+        * @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 getStoreObject( $proto, array $params = [] ) {
-               if ( !$this->externalStores || !in_array( strtolower( $proto ), $this->externalStores ) ) {
-                       // Protocol not enabled
-                       return false;
+       public function getStore( $proto, array $params = [] ) {
+               $protoLowercase = strtolower( $proto ); // normalize
+               if ( !$this->protocols || !in_array( $protoLowercase, $this->protocols ) ) {
+                       throw new ExternalStoreException( "Protocol '$proto' is not enabled." );
                }
 
                $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'] = FileBackendGroup::singleton();
+               }
+               $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
-               return class_exists( $class ) ? new $class( $params ) : false;
+               return new $class( $params );
        }
 
+       /**
+        * 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 = [] ) {
+               list( $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 ) {
+               list( , $location ) = self::splitStorageUrl( $url );
+               if ( $location == '' ) { // bad URL
+                       throw new ExternalStoreException( "Invalid URL '$url'" );
+               }
+
+               return $location;
+       }
+
+       /**
+        * @param string[] $urls
+        * @return array[] Map of (protocol => list of URLs)
+        * @throws ExternalStoreException
+        * @since 1.34
+        */
+       public function getUrlsByProtocol( array $urls ) {
+               $urlsByProtocol = [];
+               foreach ( $urls as $url ) {
+                       list( $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;
+       }
 }