MediaWiki  master
FileBackendGroup.php
Go to the documentation of this file.
1 <?php
30 
42  protected $backends = [];
43 
45  private $options;
46 
48  private $srvCache;
49 
51  private $wanCache;
52 
54  private $mimeAnalyzer;
55 
57  private $lmgFactory;
58 
60  private $tmpFileFactory;
61 
63  private $objectFactory;
64 
68  public const CONSTRUCTOR_OPTIONS = [
69  'DirectoryMode',
70  'FileBackends',
71  'ForeignFileRepos',
72  'LocalFileRepo',
73  'fallbackWikiId',
74  ];
75 
80  public static function singleton() : FileBackendGroup {
81  return MediaWikiServices::getInstance()->getFileBackendGroup();
82  }
83 
89  public static function destroySingleton() {
90  MediaWikiServices::getInstance()->resetServiceForTesting( 'FileBackendGroup' );
91  }
92 
103  public function __construct(
105  ConfiguredReadOnlyMode $configuredReadOnlyMode,
111  ObjectFactory $objectFactory
112  ) {
113  $this->options = $options;
114  $this->srvCache = $srvCache;
115  $this->wanCache = $wanCache;
116  $this->mimeAnalyzer = $mimeAnalyzer;
117  $this->lmgFactory = $lmgFactory;
118  $this->tmpFileFactory = $tmpFileFactory;
119  $this->objectFactory = $objectFactory;
120 
121  // Register explicitly defined backends
122  $this->register( $options->get( 'FileBackends' ), $configuredReadOnlyMode->getReason() );
123 
124  $autoBackends = [];
125  // Automatically create b/c backends for file repos...
126  $repos = array_merge(
127  $options->get( 'ForeignFileRepos' ), [ $options->get( 'LocalFileRepo' ) ] );
128  foreach ( $repos as $info ) {
129  $backendName = $info['backend'];
130  if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
131  continue; // already defined (or set to the object for some reason)
132  }
133  $repoName = $info['name'];
134  // Local vars that used to be FSRepo members...
135  $directory = $info['directory'];
136  $deletedDir = $info['deletedDir'] ?? false; // deletion disabled
137  $thumbDir = $info['thumbDir'] ?? "{$directory}/thumb";
138  $transcodedDir = $info['transcodedDir'] ?? "{$directory}/transcoded";
139  // Get the FS backend configuration
140  $autoBackends[] = [
141  'name' => $backendName,
142  'class' => FSFileBackend::class,
143  'lockManager' => 'fsLockManager',
144  'containerPaths' => [
145  "{$repoName}-public" => "{$directory}",
146  "{$repoName}-thumb" => $thumbDir,
147  "{$repoName}-transcoded" => $transcodedDir,
148  "{$repoName}-deleted" => $deletedDir,
149  "{$repoName}-temp" => "{$directory}/temp"
150  ],
151  'fileMode' => $info['fileMode'] ?? 0644,
152  'directoryMode' => $options->get( 'DirectoryMode' ),
153  ];
154  }
155 
156  // Register implicitly defined backends
157  $this->register( $autoBackends, $configuredReadOnlyMode->getReason() );
158  }
159 
167  protected function register( array $configs, $readOnlyReason = null ) {
168  foreach ( $configs as $config ) {
169  if ( !isset( $config['name'] ) ) {
170  throw new InvalidArgumentException( "Cannot register a backend with no name." );
171  }
172  $name = $config['name'];
173  if ( isset( $this->backends[$name] ) ) {
174  throw new LogicException( "Backend with name '$name' already registered." );
175  } elseif ( !isset( $config['class'] ) ) {
176  throw new InvalidArgumentException( "Backend with name '$name' has no class." );
177  }
178  $class = $config['class'];
179 
180  $config['domainId'] =
181  $config['domainId'] ?? $config['wikiId'] ?? $this->options->get( 'fallbackWikiId' );
182  $config['readOnly'] = $config['readOnly'] ?? $readOnlyReason;
183 
184  unset( $config['class'] ); // backend won't need this
185  $this->backends[$name] = [
186  'class' => $class,
187  'config' => $config,
188  'instance' => null
189  ];
190  }
191  }
192 
200  public function get( $name ) {
201  // Lazy-load the actual backend instance
202  if ( !isset( $this->backends[$name]['instance'] ) ) {
203  $config = $this->config( $name );
204 
205  $class = $config['class'];
206  if ( $class === FileBackendMultiWrite::class ) {
207  // @todo How can we test this? What's the intended use-case?
208  foreach ( $config['backends'] as $index => $beConfig ) {
209  if ( isset( $beConfig['template'] ) ) {
210  // Config is just a modified version of a registered backend's.
211  // This should only be used when that config is used only by this backend.
212  $config['backends'][$index] += $this->config( $beConfig['template'] );
213  }
214  }
215  }
216 
217  $this->backends[$name]['instance'] = new $class( $config );
218  }
219 
220  return $this->backends[$name]['instance'];
221  }
222 
230  public function config( $name ) {
231  if ( !isset( $this->backends[$name] ) ) {
232  throw new InvalidArgumentException( "No backend defined with the name '$name'." );
233  }
234 
235  $config = $this->backends[$name]['config'];
236 
237  return array_merge(
238  // Default backend parameters
239  [
240  'mimeCallback' => [ $this, 'guessMimeInternal' ],
241  'obResetFunc' => 'wfResetOutputBuffers',
242  'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
243  'tmpFileFactory' => $this->tmpFileFactory,
244  'statusWrapper' => [ Status::class, 'wrap' ],
245  'wanCache' => $this->wanCache,
246  'srvCache' => $this->srvCache,
247  'logger' => LoggerFactory::getInstance( 'FileOperation' ),
248  'profiler' => function ( $section ) {
249  return Profiler::instance()->scopedProfileIn( $section );
250  }
251  ],
252  // Configured backend parameters
253  $config,
254  // Resolved backend parameters
255  [
256  'class' => $this->backends[$name]['class'],
257  'lockManager' =>
258  $this->lmgFactory->getLockManagerGroup( $config['domainId'] )
259  ->get( $config['lockManager'] ),
260  'fileJournal' => isset( $config['fileJournal'] )
261  ? $this->objectFactory->createObject(
262  $config['fileJournal'] + [ 'backend' => $name ],
263  [ 'specIsArg' => true, 'assertClass' => FileJournal::class ] )
264  : new NullFileJournal
265  ]
266  );
267  }
268 
275  public function backendFromPath( $storagePath ) {
276  list( $backend, , ) = FileBackend::splitStoragePath( $storagePath );
277  if ( $backend !== null && isset( $this->backends[$backend] ) ) {
278  return $this->get( $backend );
279  }
280 
281  return null;
282  }
283 
291  public function guessMimeInternal( $storagePath, $content, $fsPath ) {
292  // Trust the extension of the storage path (caller must validate)
293  $ext = FileBackend::extensionFromPath( $storagePath );
294  $type = $this->mimeAnalyzer->guessTypesForExtension( $ext );
295  // For files without a valid extension (or one at all), inspect the contents
296  if ( !$type && $fsPath ) {
297  $type = $this->mimeAnalyzer->guessMimeType( $fsPath, false );
298  } elseif ( !$type && strlen( $content ) ) {
299  $tmpFile = $this->tmpFileFactory->newTempFSFile( 'mime_', '' );
300  file_put_contents( $tmpFile->getPath(), $content );
301  $type = $this->mimeAnalyzer->guessMimeType( $tmpFile->getPath(), false );
302  }
303  return $type ?: 'unknown/unknown';
304  }
305 }
__construct(ServiceOptions $options, ConfiguredReadOnlyMode $configuredReadOnlyMode, BagOStuff $srvCache, WANObjectCache $wanCache, MimeAnalyzer $mimeAnalyzer, LockManagerGroupFactory $lmgFactory, TempFSFileFactory $tmpFileFactory, ObjectFactory $objectFactory)
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
Simple version of FileJournal that does nothing.
MimeAnalyzer $mimeAnalyzer
static instance()
Singleton.
Definition: Profiler.php:63
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
static destroySingleton()
Destroy the singleton instance.
guessMimeInternal( $storagePath, $content, $fsPath)
A class for passing options to services.
config( $name)
Get the config array for a backend object with a given name.
getReason()
Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
ObjectFactory $objectFactory
array [] $backends
(name => (&#39;class&#39; => string, &#39;config&#39; => array, &#39;instance&#39; => object)) -var array<string,array{class:class-string,config:array,instance:object}>
backendFromPath( $storagePath)
Get an appropriate backend object from a storage path.
if(!is_readable( $file)) $ext
Definition: router.php:48
LockManagerGroupFactory $lmgFactory
$content
Definition: router.php:78
TempFSFileFactory $tmpFileFactory
WANObjectCache $wanCache
ServiceOptions $options