MediaWiki  master
ExternalStoreAccess.php
Go to the documentation of this file.
1 <?php
6 use Psr\Log\LoggerAwareInterface;
7 use Psr\Log\LoggerInterface;
8 use Psr\Log\NullLogger;
9 
22 class ExternalStoreAccess implements LoggerAwareInterface {
24  private $storeFactory;
26  private $logger;
27 
32  public function __construct( ExternalStoreFactory $factory, LoggerInterface $logger = null ) {
33  $this->storeFactory = $factory;
34  $this->logger = $logger ?: new NullLogger();
35  }
36 
37  public function setLogger( LoggerInterface $logger ) {
38  $this->logger = $logger;
39  }
40 
51  public function fetchFromURL( $url, array $params = [] ) {
52  return $this->storeFactory->getStoreForUrl( $url, $params )->fetchFromURL( $url );
53  }
54 
65  public function fetchFromURLs( array $urls, array $params = [] ) {
66  $batches = $this->storeFactory->getUrlsByProtocol( $urls );
67  $retval = [];
68  foreach ( $batches as $proto => $batchedUrls ) {
69  $store = $this->storeFactory->getStore( $proto, $params );
70  $retval += $store->batchFetchFromURLs( $batchedUrls );
71  }
72  // invalid, not found, db dead, etc.
73  $missing = array_diff( $urls, array_keys( $retval ) );
74  foreach ( $missing as $url ) {
75  $retval[$url] = false;
76  }
77 
78  return $retval;
79  }
80 
96  public function insert( $data, array $params = [], array $tryStores = null ) {
97  $tryStores = $tryStores ?? $this->storeFactory->getWriteBaseUrls();
98  if ( !$tryStores ) {
99  throw new ExternalStoreException( "List of external stores provided is empty." );
100  }
101 
102  $error = false; // track the last exception thrown
103  $readOnlyCount = 0; // track if a store was read-only
104  while ( count( $tryStores ) > 0 ) {
105  $index = mt_rand( 0, count( $tryStores ) - 1 );
106  $storeUrl = $tryStores[$index];
107 
108  $this->logger->debug( __METHOD__ . ": trying $storeUrl" );
109 
110  $store = $this->storeFactory->getStoreForUrl( $storeUrl, $params );
111  if ( $store === false ) {
112  throw new ExternalStoreException( "Invalid external storage protocol - $storeUrl" );
113  }
114 
115  $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
116  try {
117  if ( $store->isReadOnly( $location ) ) {
118  $readOnlyCount++;
119  $msg = 'read only';
120  } else {
121  $url = $store->store( $location, $data );
122  if ( strlen( $url ) ) {
123  // A store accepted the write; done!
124  return $url;
125  }
126  throw new ExternalStoreException(
127  "No URL returned by storage medium ($storeUrl)"
128  );
129  }
130  } catch ( Exception $ex ) {
131  $error = $ex;
132  $msg = 'caught ' . get_class( $error ) . ' exception: ' . $error->getMessage();
133  }
134 
135  unset( $tryStores[$index] ); // Don't try this one again!
136  $tryStores = array_values( $tryStores ); // Must have consecutive keys
137  $this->logger->error(
138  "Unable to store text to external storage {store_path} ({failure})",
139  [ 'store_path' => $storeUrl, 'failure' => $msg ]
140  );
141  }
142 
143  // We only get here when all stores failed.
144  if ( $error ) {
145  // At least one store threw an exception. Re-throw the most recent one.
146  throw $error;
147  } elseif ( $readOnlyCount ) {
148  // If no exceptions where thrown and we get here,
149  // this should mean that all stores were in read-only mode.
150  throw new ReadOnlyError();
151  } else {
152  // We shouldn't get here. If there were no failures, this method should have returned
153  // from inside the body of the loop.
154  throw new LogicException( "Unexpected failure to store text to external store" );
155  }
156  }
157 
163  public function isReadOnly( $storeUrls = null ) {
164  if ( $storeUrls === null ) {
165  $storeUrls = $this->storeFactory->getWriteBaseUrls();
166  } else {
167  $storeUrls = is_array( $storeUrls ) ? $storeUrls : [ $storeUrls ];
168  }
169 
170  if ( !$storeUrls ) {
171  return false; // no stores exists which can be "read only"
172  }
173 
174  foreach ( $storeUrls as $storeUrl ) {
175  $store = $this->storeFactory->getStoreForUrl( $storeUrl );
176  $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
177  if ( $store !== false && !$store->isReadOnly( $location ) ) {
178  return false; // at least one store is not read-only
179  }
180  }
181 
182  return true; // all stores are read-only
183  }
184 }
ReadOnlyError
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Definition: ReadOnlyError.php:28
ExternalStoreAccess\$storeFactory
ExternalStoreFactory $storeFactory
Definition: ExternalStoreAccess.php:24
ExternalStoreAccess\$logger
LoggerInterface $logger
Definition: ExternalStoreAccess.php:26
ExternalStoreAccess
Key/value blob storage for a collection of storage medium types (e.g.
Definition: ExternalStoreAccess.php:22
ExternalStoreAccess\__construct
__construct(ExternalStoreFactory $factory, LoggerInterface $logger=null)
Definition: ExternalStoreAccess.php:32
ExternalStoreAccess\fetchFromURL
fetchFromURL( $url, array $params=[])
Fetch data from given URL.
Definition: ExternalStoreAccess.php:51
ExternalStoreFactory
Definition: ExternalStoreFactory.php:15
ExternalStoreAccess\isReadOnly
isReadOnly( $storeUrls=null)
Definition: ExternalStoreAccess.php:163
ExternalStoreAccess\setLogger
setLogger(LoggerInterface $logger)
Definition: ExternalStoreAccess.php:37
ExternalStoreAccess\insert
insert( $data, array $params=[], array $tryStores=null)
Insert data into storage and return the assigned URL.
Definition: ExternalStoreAccess.php:96
ExternalStoreAccess\fetchFromURLs
fetchFromURLs(array $urls, array $params=[])
Fetch data from multiple URLs with a minimum of round trips.
Definition: ExternalStoreAccess.php:65
ExternalStoreException
Definition: ExternalStoreException.php:3