MediaWiki master
FileBackendGroup.php
Go to the documentation of this file.
1<?php
11
12use InvalidArgumentException;
13use LogicException;
26use Wikimedia\Mime\MimeAnalyzer;
30
42 protected $backends = [];
43
44 private ServiceOptions $options;
45 private BagOStuff $srvCache;
46 private WANObjectCache $wanCache;
47 private MimeAnalyzer $mimeAnalyzer;
48 private LockManagerGroupFactory $lmgFactory;
49 private TempFSFileFactory $tmpFileFactory;
50 private ?TelemetryHeadersInterface $telemetry;
51
55 public const CONSTRUCTOR_OPTIONS = [
60 'fallbackWikiId',
61 ];
62
63 public function __construct(
64 ServiceOptions $options,
65 ReadOnlyMode $readOnlyMode,
66 BagOStuff $srvCache,
67 WANObjectCache $wanCache,
68 MimeAnalyzer $mimeAnalyzer,
69 LockManagerGroupFactory $lmgFactory,
70 TempFSFileFactory $tmpFileFactory,
71 ?TelemetryHeadersInterface $telemetry = null
72 ) {
73 $this->options = $options;
74 $this->srvCache = $srvCache;
75 $this->wanCache = $wanCache;
76 $this->mimeAnalyzer = $mimeAnalyzer;
77 $this->lmgFactory = $lmgFactory;
78 $this->tmpFileFactory = $tmpFileFactory;
79 $this->telemetry = $telemetry;
80
81 // Register explicitly defined backends
82 $this->register( $options->get( MainConfigNames::FileBackends ), $readOnlyMode->getConfiguredReason() );
83
84 $autoBackends = [];
85 // Automatically create b/c backends for file repos...
86 $repos = array_merge(
88 foreach ( $repos as $info ) {
89 $backendName = $info['backend'];
90 if ( is_object( $backendName ) || isset( $this->backends[$backendName] ) ) {
91 continue; // already defined (or set to the object for some reason)
92 }
93 $repoName = $info['name'];
94 // Local vars that used to be FSRepo members...
95 $directory = $info['directory'];
96 // file deletion is disabled not set
97 $deletedDir = $info['deletedDir'] ?? false;
98 $thumbDir = $info['thumbDir'] ?? "{$directory}/thumb";
99 $transcodedDir = $info['transcodedDir'] ?? "{$directory}/transcoded";
100 $lockManager = $info['lockManager'] ?? 'fsLockManager';
101 // Get the FS backend configuration
102 $autoBackends[] = [
103 'name' => $backendName,
104 'class' => FSFileBackend::class,
105 'lockManager' => $lockManager,
106 'containerPaths' => [
107 "{$repoName}-public" => "{$directory}",
108 "{$repoName}-thumb" => $thumbDir,
109 "{$repoName}-transcoded" => $transcodedDir,
110 "{$repoName}-deleted" => $deletedDir,
111 "{$repoName}-temp" => "{$directory}/temp"
112 ],
113 'fileMode' => $info['fileMode'] ?? 0644,
114 'directoryMode' => $options->get( MainConfigNames::DirectoryMode ),
115 ];
116 }
117
118 // Register implicitly defined backends
119 $this->register( $autoBackends, $readOnlyMode->getConfiguredReason() );
120 }
121
128 protected function register( array $configs, $readOnlyReason = null ) {
129 foreach ( $configs as $config ) {
130 if ( !isset( $config['name'] ) ) {
131 throw new InvalidArgumentException( "Cannot register a backend with no name." );
132 }
133 $name = $config['name'];
134 if ( isset( $this->backends[$name] ) ) {
135 throw new LogicException( "Backend with name '$name' already registered." );
136 } elseif ( !isset( $config['class'] ) ) {
137 throw new InvalidArgumentException( "Backend with name '$name' has no class." );
138 }
139 $class = $config['class'];
140
141 $config['domainId'] ??= $config['wikiId'] ?? $this->options->get( 'fallbackWikiId' );
142 $config['readOnly'] ??= $readOnlyReason;
143
144 unset( $config['class'] ); // backend won't need this
145 $this->backends[$name] = [
146 'class' => $class,
147 'config' => $config,
148 'instance' => null
149 ];
150 }
151 }
152
159 public function get( $name ) {
160 // Lazy-load the actual backend instance
161 if ( !isset( $this->backends[$name]['instance'] ) ) {
162 $config = $this->config( $name );
163
164 $class = $config['class'];
165 // Checking old alias for compatibility with unchanged config
166 if ( $class === FileBackendMultiWrite::class || $class === \FileBackendMultiWrite::class ) {
167 // @todo How can we test this? What's the intended use-case?
168 foreach ( $config['backends'] as $index => $beConfig ) {
169 if ( isset( $beConfig['template'] ) ) {
170 // Config is just a modified version of a registered backend's.
171 // This should only be used when that config is used only by this backend.
172 $config['backends'][$index] += $this->config( $beConfig['template'] );
173 }
174 }
175 }
176
177 $this->backends[$name]['instance'] = new $class( $config );
178 }
179
180 return $this->backends[$name]['instance'];
181 }
182
189 public function config( $name ) {
190 if ( !isset( $this->backends[$name] ) ) {
191 throw new InvalidArgumentException( "No backend defined with the name '$name'." );
192 }
193
194 $config = $this->backends[$name]['config'];
195
196 return array_merge(
197 // Default backend parameters
198 [
199 'mimeCallback' => $this->guessMimeInternal( ... ),
200 'obResetFunc' => wfResetOutputBuffers( ... ),
201 'asyncHandler' => DeferredUpdates::addCallableUpdate( ... ),
202 'streamMimeFunc' => StreamFile::contentTypeFromPath( ... ),
203 'tmpFileFactory' => $this->tmpFileFactory,
204 'statusWrapper' => Status::wrap( ... ),
205 'wanCache' => $this->wanCache,
206 'srvCache' => $this->srvCache,
207 'logger' => LoggerFactory::getInstance( 'FileOperation' ),
208 'telemetry' => $this->telemetry,
209 ],
210 // Configured backend parameters
211 $config,
212 // Resolved backend parameters
213 [
214 'class' => $this->backends[$name]['class'],
215 'lockManager' =>
216 $this->lmgFactory->getLockManagerGroup( $config['domainId'] )
217 ->get( $config['lockManager'] ),
218 ]
219 );
220 }
221
228 public function backendFromPath( $storagePath ) {
229 [ $backend, , ] = FileBackend::splitStoragePath( $storagePath );
230 if ( $backend !== null && isset( $this->backends[$backend] ) ) {
231 return $this->get( $backend );
232 }
233
234 return null;
235 }
236
244 public function guessMimeInternal( $storagePath, $content, $fsPath ) {
245 // Trust the extension of the storage path (caller must validate)
246 $ext = FileBackend::extensionFromPath( $storagePath );
247 $type = $this->mimeAnalyzer->getMimeTypeFromExtensionOrNull( $ext );
248 // For files without a valid extension (or one at all), inspect the contents
249 if ( !$type && $fsPath ) {
250 $type = $this->mimeAnalyzer->guessMimeType( $fsPath, false );
251 } elseif ( !$type && $content !== null && $content !== '' ) {
252 $tmpFile = $this->tmpFileFactory->newTempFSFile( 'mime_', '' );
253 file_put_contents( $tmpFile->getPath(), $content );
254 $type = $this->mimeAnalyzer->guessMimeType( $tmpFile->getPath(), false );
255 }
256 return $type ?: 'unknown/unknown';
257 }
258}
260class_alias( FileBackendGroup::class, 'FileBackendGroup' );
wfResetOutputBuffers( $resetGzipEncoding=true)
Clear away any user-level output buffers, discarding contents.
A class for passing options to services.
Defer callable updates to run later in the PHP process.
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, ?TelemetryHeadersInterface $telemetry=null)
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.
static contentTypeFromPath( $filename, $safe=true)
Determine the file type of a file based on the path.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Class for a file system (FS) based file backend.
Proxy backend that mirrors writes to several internal backends.
Base class for all file backend classes (including multi-write backends).
static extensionFromPath( $path, $case='lowercase')
Get the final extension from a storage or FS path.
static splitStoragePath( $storagePath)
Split a storage path into a backend name, a container name, and a relative file path.
Abstract class for any ephemeral data store.
Definition BagOStuff.php:73
Multi-datacenter aware caching interface.
Determine whether a site is currently in read-only mode.
Provide Request Telemetry information.