MediaWiki  master
FileBackendGroup.php
Go to the documentation of this file.
1 <?php
29 use Wikimedia\ObjectFactory\ObjectFactory;
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  MainConfigNames::DirectoryMode,
70  MainConfigNames::FileBackends,
71  MainConfigNames::ForeignFileRepos,
72  MainConfigNames::LocalFileRepo,
73  'fallbackWikiId',
74  ];
75 
86  public function __construct(
88  ConfiguredReadOnlyMode $configuredReadOnlyMode,
94  ObjectFactory $objectFactory
95  ) {
96  $this->options = $options;
97  $this->srvCache = $srvCache;
98  $this->wanCache = $wanCache;
99  $this->mimeAnalyzer = $mimeAnalyzer;
100  $this->lmgFactory = $lmgFactory;
101  $this->tmpFileFactory = $tmpFileFactory;
102  $this->objectFactory = $objectFactory;
103 
104  // Register explicitly defined backends
105  $this->register( $options->get( MainConfigNames::FileBackends ), $configuredReadOnlyMode->getReason() );
106 
107  $autoBackends = [];
108  // Automatically create b/c backends for file repos...
109  $repos = array_merge(
110  $options->get( MainConfigNames::ForeignFileRepos ), [ $options->get( MainConfigNames::LocalFileRepo ) ] );
111  foreach ( $repos as $info ) {
112  $backendName = $info['backend'];
113  if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
114  continue; // already defined (or set to the object for some reason)
115  }
116  $repoName = $info['name'];
117  // Local vars that used to be FSRepo members...
118  $directory = $info['directory'];
119  $deletedDir = $info['deletedDir'] ?? false; // deletion disabled
120  $thumbDir = $info['thumbDir'] ?? "{$directory}/thumb";
121  $transcodedDir = $info['transcodedDir'] ?? "{$directory}/transcoded";
122  $lockManager = $info['lockManager'] ?? 'fsLockManager';
123  // Get the FS backend configuration
124  $autoBackends[] = [
125  'name' => $backendName,
126  'class' => FSFileBackend::class,
127  'lockManager' => $lockManager,
128  'containerPaths' => [
129  "{$repoName}-public" => "{$directory}",
130  "{$repoName}-thumb" => $thumbDir,
131  "{$repoName}-transcoded" => $transcodedDir,
132  "{$repoName}-deleted" => $deletedDir,
133  "{$repoName}-temp" => "{$directory}/temp"
134  ],
135  'fileMode' => $info['fileMode'] ?? 0644,
136  'directoryMode' => $options->get( MainConfigNames::DirectoryMode ),
137  ];
138  }
139 
140  // Register implicitly defined backends
141  $this->register( $autoBackends, $configuredReadOnlyMode->getReason() );
142  }
143 
151  protected function register( array $configs, $readOnlyReason = null ) {
152  foreach ( $configs as $config ) {
153  if ( !isset( $config['name'] ) ) {
154  throw new InvalidArgumentException( "Cannot register a backend with no name." );
155  }
156  $name = $config['name'];
157  if ( isset( $this->backends[$name] ) ) {
158  throw new LogicException( "Backend with name '$name' already registered." );
159  } elseif ( !isset( $config['class'] ) ) {
160  throw new InvalidArgumentException( "Backend with name '$name' has no class." );
161  }
162  $class = $config['class'];
163 
164  $config['domainId'] =
165  $config['domainId'] ?? $config['wikiId'] ?? $this->options->get( 'fallbackWikiId' );
166  $config['readOnly'] = $config['readOnly'] ?? $readOnlyReason;
167 
168  unset( $config['class'] ); // backend won't need this
169  $this->backends[$name] = [
170  'class' => $class,
171  'config' => $config,
172  'instance' => null
173  ];
174  }
175  }
176 
184  public function get( $name ) {
185  // Lazy-load the actual backend instance
186  if ( !isset( $this->backends[$name]['instance'] ) ) {
187  $config = $this->config( $name );
188 
189  $class = $config['class'];
190  if ( $class === FileBackendMultiWrite::class ) {
191  // @todo How can we test this? What's the intended use-case?
192  foreach ( $config['backends'] as $index => $beConfig ) {
193  if ( isset( $beConfig['template'] ) ) {
194  // Config is just a modified version of a registered backend's.
195  // This should only be used when that config is used only by this backend.
196  $config['backends'][$index] += $this->config( $beConfig['template'] );
197  }
198  }
199  }
200 
201  $this->backends[$name]['instance'] = new $class( $config );
202  }
203 
204  return $this->backends[$name]['instance'];
205  }
206 
214  public function config( $name ) {
215  if ( !isset( $this->backends[$name] ) ) {
216  throw new InvalidArgumentException( "No backend defined with the name '$name'." );
217  }
218 
219  $config = $this->backends[$name]['config'];
220 
221  return array_merge(
222  // Default backend parameters
223  [
224  'mimeCallback' => [ $this, 'guessMimeInternal' ],
225  'obResetFunc' => 'wfResetOutputBuffers',
226  'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
227  'tmpFileFactory' => $this->tmpFileFactory,
228  'statusWrapper' => [ Status::class, 'wrap' ],
229  'wanCache' => $this->wanCache,
230  'srvCache' => $this->srvCache,
231  'logger' => LoggerFactory::getInstance( 'FileOperation' ),
232  'profiler' => static function ( $section ) {
233  return Profiler::instance()->scopedProfileIn( $section );
234  }
235  ],
236  // Configured backend parameters
237  $config,
238  // Resolved backend parameters
239  [
240  'class' => $this->backends[$name]['class'],
241  'lockManager' =>
242  $this->lmgFactory->getLockManagerGroup( $config['domainId'] )
243  ->get( $config['lockManager'] ),
244  ]
245  );
246  }
247 
254  public function backendFromPath( $storagePath ) {
255  list( $backend, , ) = FileBackend::splitStoragePath( $storagePath );
256  if ( $backend !== null && isset( $this->backends[$backend] ) ) {
257  return $this->get( $backend );
258  }
259 
260  return null;
261  }
262 
270  public function guessMimeInternal( $storagePath, $content, $fsPath ) {
271  // Trust the extension of the storage path (caller must validate)
272  $ext = FileBackend::extensionFromPath( $storagePath );
273  $type = $this->mimeAnalyzer->getMimeTypeFromExtensionOrNull( $ext );
274  // For files without a valid extension (or one at all), inspect the contents
275  if ( !$type && $fsPath ) {
276  $type = $this->mimeAnalyzer->guessMimeType( $fsPath, false );
277  } elseif ( !$type && strlen( $content ) ) {
278  $tmpFile = $this->tmpFileFactory->newTempFSFile( 'mime_', '' );
279  file_put_contents( $tmpFile->getPath(), $content );
280  $type = $this->mimeAnalyzer->guessMimeType( $tmpFile->getPath(), false );
281  }
282  return $type ?: 'unknown/unknown';
283  }
284 }
Class representing a cache/ephemeral data store.
Definition: BagOStuff.php:87
A read-only mode service which does not depend on LoadBalancer.
getReason()
Get the value of $wgReadOnly or the contents of $wgReadOnlyFile.
Class to handle file backend registration.
WANObjectCache $wanCache
ObjectFactory $objectFactory
MimeAnalyzer $mimeAnalyzer
ServiceOptions $options
config( $name)
Get the config array for a backend object with a given name.
TempFSFileFactory $tmpFileFactory
guessMimeInternal( $storagePath, $content, $fsPath)
__construct(ServiceOptions $options, ConfiguredReadOnlyMode $configuredReadOnlyMode, BagOStuff $srvCache, WANObjectCache $wanCache, MimeAnalyzer $mimeAnalyzer, LockManagerGroupFactory $lmgFactory, TempFSFileFactory $tmpFileFactory, ObjectFactory $objectFactory)
LockManagerGroupFactory $lmgFactory
backendFromPath( $storagePath)
Get an appropriate backend object from a storage path.
array[] $backends
(name => ('class' => string, 'config' => array, 'instance' => object))
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
A class for passing options to services.
PSR-3 logger instance factory.
A class containing constants representing the names of configuration variables.
Implements functions related to MIME types such as detection and mapping to file extension.
static instance()
Singleton.
Definition: Profiler.php:69
Multi-datacenter aware caching interface.
$content
Definition: router.php:76
if(!is_readable( $file)) $ext
Definition: router.php:48