MediaWiki master
ExternalStoreAccess.php
Go to the documentation of this file.
1<?php
8
9use LogicException;
11use Psr\Log\LoggerAwareInterface;
12use Psr\Log\LoggerInterface;
13use Psr\Log\NullLogger;
14
32class ExternalStoreAccess implements LoggerAwareInterface {
34 private $storeFactory;
36 private $logger;
37
42 public function __construct( ExternalStoreFactory $factory, ?LoggerInterface $logger = null ) {
43 $this->storeFactory = $factory;
44 $this->logger = $logger ?? new NullLogger();
45 }
46
47 public function setLogger( LoggerInterface $logger ): void {
48 $this->logger = $logger;
49 }
50
61 public function fetchFromURL( $url, array $params = [] ) {
62 return $this->storeFactory->getStoreForUrl( $url, $params )->fetchFromURL( $url );
63 }
64
75 public function fetchFromURLs( array $urls, array $params = [] ) {
76 $batches = $this->storeFactory->getUrlsByProtocol( $urls );
77 $retval = [];
78 foreach ( $batches as $proto => $batchedUrls ) {
79 $store = $this->storeFactory->getStore( $proto, $params );
80 $retval += $store->batchFetchFromURLs( $batchedUrls );
81 }
82 // invalid, not found, db dead, etc.
83 $missing = array_diff( $urls, array_keys( $retval ) );
84 foreach ( $missing as $url ) {
85 $retval[$url] = false;
86 }
87
88 return $retval;
89 }
90
106 public function insert( $data, array $params = [], ?array $tryStores = null ) {
107 $tryStores ??= $this->storeFactory->getWriteBaseUrls();
108 if ( !$tryStores ) {
109 throw new ExternalStoreException( "List of external stores provided is empty." );
110 }
111
112 $error = false; // track the last exception thrown
113 $readOnlyCount = 0; // track if a store was read-only
114 while ( count( $tryStores ) > 0 ) {
115 $index = mt_rand( 0, count( $tryStores ) - 1 );
116 $storeUrl = $tryStores[$index];
117
118 $this->logger->debug( __METHOD__ . ": trying $storeUrl" );
119
120 $store = $this->storeFactory->getStoreForUrl( $storeUrl, $params );
121 if ( $store === false ) {
122 throw new ExternalStoreException( "Invalid external storage protocol - $storeUrl" );
123 }
124
125 $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
126 try {
127 if ( $store->isReadOnly( $location ) ) {
128 $readOnlyCount++;
129 $msg = 'read only';
130 } else {
131 $url = $store->store( $location, $data );
132 if ( $url !== false && $url !== '' ) {
133 // A store accepted the write; done!
134 return $url;
135 }
136 throw new ExternalStoreException(
137 "No URL returned by storage medium ($storeUrl)"
138 );
139 }
140 } catch ( ExternalStoreException $ex ) {
141 $error = $ex;
142 $msg = 'caught ' . get_class( $error ) . ' exception: ' . $error->getMessage();
143 }
144
145 unset( $tryStores[$index] ); // Don't try this one again!
146 $tryStores = array_values( $tryStores ); // Must have consecutive keys
147 $this->logger->error(
148 "Unable to store text to external storage {store_path} ({failure})",
149 [ 'store_path' => $storeUrl, 'failure' => $msg ]
150 );
151 }
152
153 // We only get here when all stores failed.
154 if ( $error ) {
155 // At least one store threw an exception. Re-throw the most recent one.
156 throw $error;
157 } elseif ( $readOnlyCount ) {
158 // If no exceptions where thrown and we get here,
159 // this should mean that all stores were in read-only mode.
160 throw new ReadOnlyError();
161 } else {
162 // We shouldn't get here. If there were no failures, this method should have returned
163 // from inside the body of the loop.
164 throw new LogicException( "Unexpected failure to store text to external store" );
165 }
166 }
167
173 public function isReadOnly( $storeUrls = null ) {
174 if ( $storeUrls === null ) {
175 $storeUrls = $this->storeFactory->getWriteBaseUrls();
176 } else {
177 $storeUrls = is_array( $storeUrls ) ? $storeUrls : [ $storeUrls ];
178 }
179
180 if ( !$storeUrls ) {
181 return false; // no stores exists which can be "read only"
182 }
183
184 foreach ( $storeUrls as $storeUrl ) {
185 $store = $this->storeFactory->getStoreForUrl( $storeUrl );
186 $location = $this->storeFactory->getStoreLocationFromUrl( $storeUrl );
187 if ( $store !== false && !$store->isReadOnly( $location ) ) {
188 return false; // at least one store is not read-only
189 }
190 }
191
192 return true; // all stores are read-only
193 }
194}
195
197class_alias( ExternalStoreAccess::class, 'ExternalStoreAccess' );
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
This is the main interface for fetching or inserting objects with ExternalStore.
fetchFromURLs(array $urls, array $params=[])
Fetch data from multiple URLs with a minimum of round trips.
fetchFromURL( $url, array $params=[])
Fetch data from given URL.
insert( $data, array $params=[], ?array $tryStores=null)
Insert data into storage and return the assigned URL.
__construct(ExternalStoreFactory $factory, ?LoggerInterface $logger=null)