MediaWiki REL1_39
FileBackendGroup.php
Go to the documentation of this file.
1<?php
29use 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(
87 ServiceOptions $options,
88 ConfiguredReadOnlyMode $configuredReadOnlyMode,
89 BagOStuff $srvCache,
90 WANObjectCache $wanCache,
91 MimeAnalyzer $mimeAnalyzer,
92 LockManagerGroupFactory $lmgFactory,
93 TempFSFileFactory $tmpFileFactory,
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 && $content !== null && $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:85
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.
config( $name)
Get the config array for a backend object with a given name.
guessMimeInternal( $storagePath, $content, $fsPath)
__construct(ServiceOptions $options, ConfiguredReadOnlyMode $configuredReadOnlyMode, BagOStuff $srvCache, WANObjectCache $wanCache, MimeAnalyzer $mimeAnalyzer, LockManagerGroupFactory $lmgFactory, TempFSFileFactory $tmpFileFactory, ObjectFactory $objectFactory)
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.
Multi-datacenter aware caching interface.
$content
Definition router.php:76
if(!is_readable( $file)) $ext
Definition router.php:48