MediaWiki REL1_37
FileBackendGroup.php
Go to the documentation of this file.
1<?php
29use Wikimedia\ObjectFactory;
30
42 protected $backends = [];
43
45 private $options;
46
48 private $srvCache;
49
51 private $wanCache;
52
55
57 private $lmgFactory;
58
61
64
68 public const CONSTRUCTOR_OPTIONS = [
69 'DirectoryMode',
70 'FileBackends',
71 'ForeignFileRepos',
72 'LocalFileRepo',
73 'fallbackWikiId',
74 ];
75
82 public static function singleton(): FileBackendGroup {
83 wfDeprecated( __METHOD__, '1.35' );
84 return MediaWikiServices::getInstance()->getFileBackendGroup();
85 }
86
91 public static function destroySingleton() {
92 wfDeprecated( __METHOD__, '1.35' );
93 MediaWikiServices::getInstance()->resetServiceForTesting( 'FileBackendGroup' );
94 }
95
106 public function __construct(
107 ServiceOptions $options,
108 ConfiguredReadOnlyMode $configuredReadOnlyMode,
109 BagOStuff $srvCache,
110 WANObjectCache $wanCache,
111 MimeAnalyzer $mimeAnalyzer,
112 LockManagerGroupFactory $lmgFactory,
113 TempFSFileFactory $tmpFileFactory,
114 ObjectFactory $objectFactory
115 ) {
116 $this->options = $options;
117 $this->srvCache = $srvCache;
118 $this->wanCache = $wanCache;
119 $this->mimeAnalyzer = $mimeAnalyzer;
120 $this->lmgFactory = $lmgFactory;
121 $this->tmpFileFactory = $tmpFileFactory;
122 $this->objectFactory = $objectFactory;
123
124 // Register explicitly defined backends
125 $this->register( $options->get( 'FileBackends' ), $configuredReadOnlyMode->getReason() );
126
127 $autoBackends = [];
128 // Automatically create b/c backends for file repos...
129 $repos = array_merge(
130 $options->get( 'ForeignFileRepos' ), [ $options->get( 'LocalFileRepo' ) ] );
131 foreach ( $repos as $info ) {
132 $backendName = $info['backend'];
133 if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
134 continue; // already defined (or set to the object for some reason)
135 }
136 $repoName = $info['name'];
137 // Local vars that used to be FSRepo members...
138 $directory = $info['directory'];
139 $deletedDir = $info['deletedDir'] ?? false; // deletion disabled
140 $thumbDir = $info['thumbDir'] ?? "{$directory}/thumb";
141 $transcodedDir = $info['transcodedDir'] ?? "{$directory}/transcoded";
142 // Get the FS backend configuration
143 $autoBackends[] = [
144 'name' => $backendName,
145 'class' => FSFileBackend::class,
146 'lockManager' => 'fsLockManager',
147 'containerPaths' => [
148 "{$repoName}-public" => "{$directory}",
149 "{$repoName}-thumb" => $thumbDir,
150 "{$repoName}-transcoded" => $transcodedDir,
151 "{$repoName}-deleted" => $deletedDir,
152 "{$repoName}-temp" => "{$directory}/temp"
153 ],
154 'fileMode' => $info['fileMode'] ?? 0644,
155 'directoryMode' => $options->get( 'DirectoryMode' ),
156 ];
157 }
158
159 // Register implicitly defined backends
160 $this->register( $autoBackends, $configuredReadOnlyMode->getReason() );
161 }
162
170 protected function register( array $configs, $readOnlyReason = null ) {
171 foreach ( $configs as $config ) {
172 if ( !isset( $config['name'] ) ) {
173 throw new InvalidArgumentException( "Cannot register a backend with no name." );
174 }
175 $name = $config['name'];
176 if ( isset( $this->backends[$name] ) ) {
177 throw new LogicException( "Backend with name '$name' already registered." );
178 } elseif ( !isset( $config['class'] ) ) {
179 throw new InvalidArgumentException( "Backend with name '$name' has no class." );
180 }
181 $class = $config['class'];
182
183 $config['domainId'] =
184 $config['domainId'] ?? $config['wikiId'] ?? $this->options->get( 'fallbackWikiId' );
185 $config['readOnly'] = $config['readOnly'] ?? $readOnlyReason;
186
187 unset( $config['class'] ); // backend won't need this
188 $this->backends[$name] = [
189 'class' => $class,
190 'config' => $config,
191 'instance' => null
192 ];
193 }
194 }
195
203 public function get( $name ) {
204 // Lazy-load the actual backend instance
205 if ( !isset( $this->backends[$name]['instance'] ) ) {
206 $config = $this->config( $name );
207
208 $class = $config['class'];
209 if ( $class === FileBackendMultiWrite::class ) {
210 // @todo How can we test this? What's the intended use-case?
211 foreach ( $config['backends'] as $index => $beConfig ) {
212 if ( isset( $beConfig['template'] ) ) {
213 // Config is just a modified version of a registered backend's.
214 // This should only be used when that config is used only by this backend.
215 $config['backends'][$index] += $this->config( $beConfig['template'] );
216 }
217 }
218 }
219
220 $this->backends[$name]['instance'] = new $class( $config );
221 }
222
223 return $this->backends[$name]['instance'];
224 }
225
233 public function config( $name ) {
234 if ( !isset( $this->backends[$name] ) ) {
235 throw new InvalidArgumentException( "No backend defined with the name '$name'." );
236 }
237
238 $config = $this->backends[$name]['config'];
239
240 return array_merge(
241 // Default backend parameters
242 [
243 'mimeCallback' => [ $this, 'guessMimeInternal' ],
244 'obResetFunc' => 'wfResetOutputBuffers',
245 'streamMimeFunc' => [ StreamFile::class, 'contentTypeFromPath' ],
246 'tmpFileFactory' => $this->tmpFileFactory,
247 'statusWrapper' => [ Status::class, 'wrap' ],
248 'wanCache' => $this->wanCache,
249 'srvCache' => $this->srvCache,
250 'logger' => LoggerFactory::getInstance( 'FileOperation' ),
251 'profiler' => static function ( $section ) {
252 return Profiler::instance()->scopedProfileIn( $section );
253 }
254 ],
255 // Configured backend parameters
256 $config,
257 // Resolved backend parameters
258 [
259 'class' => $this->backends[$name]['class'],
260 'lockManager' =>
261 $this->lmgFactory->getLockManagerGroup( $config['domainId'] )
262 ->get( $config['lockManager'] ),
263 'fileJournal' => isset( $config['fileJournal'] )
264 ? $this->objectFactory->createObject(
265 $config['fileJournal'] + [ 'backend' => $name ],
266 [ 'specIsArg' => true, 'assertClass' => FileJournal::class ] )
267 : new NullFileJournal
268 ]
269 );
270 }
271
278 public function backendFromPath( $storagePath ) {
279 list( $backend, , ) = FileBackend::splitStoragePath( $storagePath );
280 if ( $backend !== null && isset( $this->backends[$backend] ) ) {
281 return $this->get( $backend );
282 }
283
284 return null;
285 }
286
294 public function guessMimeInternal( $storagePath, $content, $fsPath ) {
295 // Trust the extension of the storage path (caller must validate)
296 $ext = FileBackend::extensionFromPath( $storagePath );
297 $type = $this->mimeAnalyzer->getMimeTypeFromExtensionOrNull( $ext );
298 // For files without a valid extension (or one at all), inspect the contents
299 if ( !$type && $fsPath ) {
300 $type = $this->mimeAnalyzer->guessMimeType( $fsPath, false );
301 } elseif ( !$type && $content !== null && $content !== '' ) {
302 $tmpFile = $this->tmpFileFactory->newTempFSFile( 'mime_', '' );
303 file_put_contents( $tmpFile->getPath(), $content );
304 $type = $this->mimeAnalyzer->guessMimeType( $tmpFile->getPath(), false );
305 }
306 return $type ?: 'unknown/unknown';
307 }
308}
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Class representing a cache/ephemeral data store.
Definition BagOStuff.php:86
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.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Simple version of FileJournal that does nothing.
Multi-datacenter aware caching interface.
$content
Definition router.php:76
if(!is_readable( $file)) $ext
Definition router.php:48