MediaWiki master
FileBackendGroup.php
Go to the documentation of this file.
1<?php
25
28use InvalidArgumentException;
29use LogicException;
37use MimeAnalyzer;
38use Profiler;
42use Wikimedia\ObjectFactory\ObjectFactory;
44
56 protected $backends = [];
57
59 private $options;
60
62 private $srvCache;
63
65 private $wanCache;
66
68 private $mimeAnalyzer;
69
71 private $lmgFactory;
72
74 private $tmpFileFactory;
75
77 private $objectFactory;
78
82 public const CONSTRUCTOR_OPTIONS = [
87 'fallbackWikiId',
88 ];
89
100 public function __construct(
101 ServiceOptions $options,
102 ReadOnlyMode $readOnlyMode,
103 BagOStuff $srvCache,
104 WANObjectCache $wanCache,
105 MimeAnalyzer $mimeAnalyzer,
106 LockManagerGroupFactory $lmgFactory,
107 TempFSFileFactory $tmpFileFactory,
108 ObjectFactory $objectFactory
109 ) {
110 $this->options = $options;
111 $this->srvCache = $srvCache;
112 $this->wanCache = $wanCache;
113 $this->mimeAnalyzer = $mimeAnalyzer;
114 $this->lmgFactory = $lmgFactory;
115 $this->tmpFileFactory = $tmpFileFactory;
116 $this->objectFactory = $objectFactory;
117
118 // Register explicitly defined backends
119 $this->register( $options->get( MainConfigNames::FileBackends ), $readOnlyMode->getConfiguredReason() );
120
121 $autoBackends = [];
122 // Automatically create b/c backends for file repos...
123 $repos = array_merge(
125 foreach ( $repos as $info ) {
126 $backendName = $info['backend'];
127 if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
128 continue; // already defined (or set to the object for some reason)
129 }
130 $repoName = $info['name'];
131 // Local vars that used to be FSRepo members...
132 $directory = $info['directory'];
133 $deletedDir = $info['deletedDir'] ?? false; // deletion disabled
134 $thumbDir = $info['thumbDir'] ?? "{$directory}/thumb";
135 $transcodedDir = $info['transcodedDir'] ?? "{$directory}/transcoded";
136 $lockManager = $info['lockManager'] ?? 'fsLockManager';
137 // Get the FS backend configuration
138 $autoBackends[] = [
139 'name' => $backendName,
140 'class' => FSFileBackend::class,
141 'lockManager' => $lockManager,
142 'containerPaths' => [
143 "{$repoName}-public" => "{$directory}",
144 "{$repoName}-thumb" => $thumbDir,
145 "{$repoName}-transcoded" => $transcodedDir,
146 "{$repoName}-deleted" => $deletedDir,
147 "{$repoName}-temp" => "{$directory}/temp"
148 ],
149 'fileMode' => $info['fileMode'] ?? 0644,
150 'directoryMode' => $options->get( MainConfigNames::DirectoryMode ),
151 ];
152 }
153
154 // Register implicitly defined backends
155 $this->register( $autoBackends, $readOnlyMode->getConfiguredReason() );
156 }
157
165 protected function register( array $configs, $readOnlyReason = null ) {
166 foreach ( $configs as $config ) {
167 if ( !isset( $config['name'] ) ) {
168 throw new InvalidArgumentException( "Cannot register a backend with no name." );
169 }
170 $name = $config['name'];
171 if ( isset( $this->backends[$name] ) ) {
172 throw new LogicException( "Backend with name '$name' already registered." );
173 } elseif ( !isset( $config['class'] ) ) {
174 throw new InvalidArgumentException( "Backend with name '$name' has no class." );
175 }
176 $class = $config['class'];
177
178 $config['domainId'] ??= $config['wikiId'] ?? $this->options->get( 'fallbackWikiId' );
179 $config['readOnly'] ??= $readOnlyReason;
180
181 unset( $config['class'] ); // backend won't need this
182 $this->backends[$name] = [
183 'class' => $class,
184 'config' => $config,
185 'instance' => null
186 ];
187 }
188 }
189
197 public function get( $name ) {
198 // Lazy-load the actual backend instance
199 if ( !isset( $this->backends[$name]['instance'] ) ) {
200 $config = $this->config( $name );
201
202 $class = $config['class'];
203 if ( $class === FileBackendMultiWrite::class ) {
204 // @todo How can we test this? What's the intended use-case?
205 foreach ( $config['backends'] as $index => $beConfig ) {
206 if ( isset( $beConfig['template'] ) ) {
207 // Config is just a modified version of a registered backend's.
208 // This should only be used when that config is used only by this backend.
209 $config['backends'][$index] += $this->config( $beConfig['template'] );
210 }
211 }
212 }
213
214 $this->backends[$name]['instance'] = new $class( $config );
215 }
216
217 return $this->backends[$name]['instance'];
218 }
219
227 public function config( $name ) {
228 if ( !isset( $this->backends[$name] ) ) {
229 throw new InvalidArgumentException( "No backend defined with the name '$name'." );
230 }
231
232 $config = $this->backends[$name]['config'];
233
234 return array_merge(
235 // Default backend parameters
236 [
237 'mimeCallback' => [ $this, 'guessMimeInternal' ],
238 'obResetFunc' => 'wfResetOutputBuffers',
239 'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
240 'tmpFileFactory' => $this->tmpFileFactory,
241 'statusWrapper' => [ Status::class, 'wrap' ],
242 'wanCache' => $this->wanCache,
243 'srvCache' => $this->srvCache,
244 'logger' => LoggerFactory::getInstance( 'FileOperation' ),
245 'profiler' => static function ( $section ) {
246 return Profiler::instance()->scopedProfileIn( $section );
247 }
248 ],
249 // Configured backend parameters
250 $config,
251 // Resolved backend parameters
252 [
253 'class' => $this->backends[$name]['class'],
254 'lockManager' =>
255 $this->lmgFactory->getLockManagerGroup( $config['domainId'] )
256 ->get( $config['lockManager'] ),
257 ]
258 );
259 }
260
267 public function backendFromPath( $storagePath ) {
268 [ $backend, , ] = FileBackend::splitStoragePath( $storagePath );
269 if ( $backend !== null && isset( $this->backends[$backend] ) ) {
270 return $this->get( $backend );
271 }
272
273 return null;
274 }
275
283 public function guessMimeInternal( $storagePath, $content, $fsPath ) {
284 // Trust the extension of the storage path (caller must validate)
285 $ext = FileBackend::extensionFromPath( $storagePath );
286 $type = $this->mimeAnalyzer->getMimeTypeFromExtensionOrNull( $ext );
287 // For files without a valid extension (or one at all), inspect the contents
288 if ( !$type && $fsPath ) {
289 $type = $this->mimeAnalyzer->guessMimeType( $fsPath, false );
290 } elseif ( !$type && $content !== null && $content !== '' ) {
291 $tmpFile = $this->tmpFileFactory->newTempFSFile( 'mime_', '' );
292 file_put_contents( $tmpFile->getPath(), $content );
293 $type = $this->mimeAnalyzer->guessMimeType( $tmpFile->getPath(), false );
294 }
295 return $type ?: 'unknown/unknown';
296 }
297}
299class_alias( FileBackendGroup::class, 'FileBackendGroup' );
Class for a file system (FS) based file backend.
Proxy backend that mirrors writes to several internal backends.
A class for passing options to services.
Class to handle file backend registration.
backendFromPath( $storagePath)
Get an appropriate backend object from a storage path.
config( $name)
Get the config array for a backend object with a given name.
__construct(ServiceOptions $options, ReadOnlyMode $readOnlyMode, BagOStuff $srvCache, WANObjectCache $wanCache, MimeAnalyzer $mimeAnalyzer, LockManagerGroupFactory $lmgFactory, TempFSFileFactory $tmpFileFactory, ObjectFactory $objectFactory)
array[] $backends
(name => ('class' => string, 'config' => array, 'instance' => object))
guessMimeInternal( $storagePath, $content, $fsPath)
Create PSR-3 logger objects.
A class containing constants representing the names of configuration variables.
const FileBackends
Name constant for the FileBackends setting, for use with Config::get()
const LocalFileRepo
Name constant for the LocalFileRepo setting, for use with Config::get()
const ForeignFileRepos
Name constant for the ForeignFileRepos setting, for use with Config::get()
const DirectoryMode
Name constant for the DirectoryMode setting, for use with Config::get()
Functions related to the output of file content.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:54
Profiler base class that defines the interface and some shared functionality.
Definition Profiler.php:37
static instance()
Definition Profiler.php:105
Multi-datacenter aware caching interface.
Base class for all file backend classes (including multi-write backends).
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:88
Determine whether a site is currently in read-only mode.