* @ingroup FileBackend
* @author Aaron Schulz
*/
+use Psr\Log\LoggerAwareInterface;
+use Psr\Log\LoggerInterface;
/**
* @brief Base class for all file backend classes (including multi-write backends).
* Outside callers can assume that all backends will have these functions.
*
* All "storage paths" are of the format "mwstore://<backend>/<container>/<path>".
- * The "backend" portion is unique name for MediaWiki to refer to a backend, while
+ * The "backend" portion is unique name for the application to refer to a backend, while
* the "container" portion is a top-level directory of the backend. The "path" portion
* is a relative path that uses UNIX file system (FS) notation, though any particular
* backend may not actually be using a local filesystem. Therefore, the relative paths
* are only virtual.
*
- * Backend contents are stored under wiki-specific container names by default.
- * Global (qualified) backends are achieved by configuring the "wiki ID" to a constant.
+ * Backend contents are stored under "domain"-specific container names by default.
+ * A domain is simply a logical umbrella for entities, such as those belonging to a certain
+ * application or portion of a website, for example. A domain can be local or global.
+ * Global (qualified) backends are achieved by configuring the "domain ID" to a constant.
+ * Global domains are simpler, but local domains can be used by choosing a domain ID based on
+ * the current context, such as which language of a website is being used.
+ *
* For legacy reasons, the FSFileBackend class allows manually setting the paths of
- * containers to ones that do not respect the "wiki ID".
+ * containers to ones that do not respect the "domain ID".
*
* In key/value (object) stores, containers are the only hierarchy (the rest is emulated).
* FS-based backends are somewhat more restrictive due to the existence of real
* @ingroup FileBackend
* @since 1.19
*/
-abstract class FileBackend {
+abstract class FileBackend implements LoggerAwareInterface {
/** @var string Unique backend name */
protected $name;
- /** @var string Unique wiki name */
- protected $wikiId;
+ /** @var string Unique domain name */
+ protected $domainId;
/** @var string Read-only explanation message */
protected $readOnly;
/** @var LockManager */
protected $lockManager;
-
/** @var FileJournal */
protected $fileJournal;
+ /** @var LoggerInterface */
+ protected $logger;
+ /** @var object|string Class name or object With profileIn/profileOut methods */
+ protected $profiler;
+ /** @var callable */
+ protected $obResetFunc;
+ /** @var callable */
+ protected $streamMimeFunc;
/** @var callable */
protected $statusWrapper;
* This should only be called from within FileBackendGroup.
*
* @param array $config Parameters include:
- * - name : The unique name of this backend.
- * This should consist of alphanumberic, '-', and '_' characters.
- * This name should not be changed after use (e.g. with journaling).
- * Note that the name is *not* used in actual container names.
- * - wikiId : Prefix to container names that is unique to this backend.
- * It should only consist of alphanumberic, '-', and '_' characters.
- * This ID is what avoids collisions if multiple logical backends
- * use the same storage system, so this should be set carefully.
+ * - name : The unique name of this backend.
+ * This should consist of alphanumberic, '-', and '_' characters.
+ * This name should not be changed after use (e.g. with journaling).
+ * Note that the name is *not* used in actual container names.
+ * - domainId : Prefix to container names that is unique to this backend.
+ * It should only consist of alphanumberic, '-', and '_' characters.
+ * This ID is what avoids collisions if multiple logical backends
+ * use the same storage system, so this should be set carefully.
* - lockManager : LockManager object to use for any file locking.
- * If not provided, then no file locking will be enforced.
+ * If not provided, then no file locking will be enforced.
* - fileJournal : FileJournal object to use for logging changes to files.
- * If not provided, then change journaling will be disabled.
- * - readOnly : Write operations are disallowed if this is a non-empty string.
- * It should be an explanation for the backend being read-only.
+ * If not provided, then change journaling will be disabled.
+ * - readOnly : Write operations are disallowed if this is a non-empty string.
+ * It should be an explanation for the backend being read-only.
* - parallelize : When to do file operations in parallel (when possible).
- * Allowed values are "implicit", "explicit" and "off".
+ * Allowed values are "implicit", "explicit" and "off".
* - concurrency : How many file operations can be done in parallel.
* - tmpDirectory : Directory to use for temporary files. If this is not set or null,
- * then the backend will try to discover a usable temporary directory.
- * @throws FileBackendException
+ * then the backend will try to discover a usable temporary directory.
+ * - obResetFunc : alternative callback to clear the output buffer
+ * - streamMimeFunc : alternative method to determine the content type from the path
+ * - logger : Optional PSR logger object.
+ * - profiler : Optional class name or object With profileIn/profileOut methods.
+ * @throws InvalidArgumentException
*/
public function __construct( array $config ) {
$this->name = $config['name'];
- $this->wikiId = $config['wikiId']; // e.g. "my_wiki-en_"
+ $this->domainId = isset( $config['domainId'] )
+ ? $config['domainId'] // e.g. "my_wiki-en_"
+ : $config['wikiId']; // b/c alias
if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) {
- throw new FileBackendException( "Backend name '{$this->name}' is invalid." );
- } elseif ( !is_string( $this->wikiId ) ) {
- throw new FileBackendException( "Backend wiki ID not provided for '{$this->name}'." );
+ throw new InvalidArgumentException( "Backend name '{$this->name}' is invalid." );
+ } elseif ( !is_string( $this->domainId ) ) {
+ throw new InvalidArgumentException(
+ "Backend domain ID not provided for '{$this->name}'." );
}
$this->lockManager = isset( $config['lockManager'] )
? $config['lockManager']
$this->concurrency = isset( $config['concurrency'] )
? (int)$config['concurrency']
: 50;
+ $this->obResetFunc = isset( $params['obResetFunc'] )
+ ? $params['obResetFunc']
+ : [ $this, 'resetOutputBuffer' ];
+ $this->streamMimeFunc = isset( $params['streamMimeFunc'] )
+ ? $params['streamMimeFunc']
+ : null;
+ $this->statusWrapper = isset( $config['statusWrapper'] ) ? $config['statusWrapper'] : null;
+
+ $this->profiler = isset( $params['profiler'] ) ? $params['profiler'] : null;
+ $this->logger = isset( $config['logger'] ) ? $config['logger'] : new \Psr\Log\NullLogger();
$this->statusWrapper = isset( $config['statusWrapper'] ) ? $config['statusWrapper'] : null;
$this->tmpDirectory = isset( $config['tmpDirectory'] ) ? $config['tmpDirectory'] : null;
}
+ public function setLogger( LoggerInterface $logger ) {
+ $this->logger = $logger;
+ }
+
/**
* Get the unique backend name.
* We may have multiple different backends of the same type.
}
/**
- * Get the wiki identifier used for this backend (possibly empty).
- * Note that this might *not* be in the same format as wfWikiID().
+ * Get the domain identifier used for this backend (possibly empty).
*
* @return string
+ * @since 1.28
+ */
+ final public function getDomainId() {
+ return $this->domainId;
+ }
+
+ /**
+ * Alias to getDomainId()
+ * @return string
* @since 1.20
*/
final public function getWikiId() {
- return $this->wikiId;
+ return $this->getDomainId();
}
/**
$type = strtolower( $type );
if ( !in_array( $type, [ 'inline', 'attachment' ] ) ) {
- throw new FileBackendError( "Invalid Content-Disposition type '$type'." );
+ throw new InvalidArgumentException( "Invalid Content-Disposition type '$type'." );
}
$parts[] = $type;
final protected function wrapStatus( StatusValue $sv ) {
return $this->statusWrapper ? call_user_func( $this->statusWrapper, $sv ) : $sv;
}
+
+ /**
+ * @param string $section
+ * @return ScopedCallback|null
+ */
+ protected function scopedProfileSection( $section ) {
+ if ( $this->profiler ) {
+ call_user_func( [ $this->profiler, 'profileIn' ], $section );
+ return new ScopedCallback( [ $this->profiler, 'profileOut' ] );
+ }
+
+ return null;
+ }
+
+ protected function resetOutputBuffer() {
+ while ( ob_get_status() ) {
+ if ( !ob_end_clean() ) {
+ // Could not remove output buffer handler; abort now
+ // to avoid getting in some kind of infinite loop.
+ break;
+ }
+ }
+ }
}