MediaWiki master
LocalRepo.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\FileRepo;
8
9use Closure;
10use InvalidArgumentException;
26use stdClass;
35
45class LocalRepo extends FileRepo {
47 protected $fileFactory = [ LocalFile::class, 'newFromTitle' ];
49 protected $fileFactoryKey = [ LocalFile::class, 'newFromKey' ];
51 protected $fileFromRowFactory = [ LocalFile::class, 'newFromRow' ];
53 protected $oldFileFromRowFactory = [ OldLocalFile::class, 'newFromRow' ];
55 protected $oldFileFactory = [ OldLocalFile::class, 'newFromTitle' ];
57 protected $oldFileFactoryKey = [ OldLocalFile::class, 'newFromKey' ];
58
60 protected $dbDomain;
64
66 protected $blobStore;
67
69 protected $useJsonMetadata = true;
70
72 protected $useSplitMetadata = false;
73
75 protected $splitMetadataThreshold = 1000;
76
78 protected $updateCompatibleMetadata = false;
79
81 protected $reserializeMetadata = false;
82
83 public function __construct( ?array $info = null ) {
84 parent::__construct( $info );
85
86 $this->dbDomain = WikiMap::getCurrentWikiDbDomain()->getId();
87 $this->hasAccessibleSharedCache = true;
88
89 $this->hasSha1Storage = ( $info['storageLayout'] ?? null ) === 'sha1';
90 $this->dbProvider = MediaWikiServices::getInstance()->getConnectionProvider();
91
92 if ( $this->hasSha1Storage() ) {
93 $this->backend = new FileBackendDBRepoWrapper( [
94 'backend' => $this->backend,
95 'repoName' => $this->name,
96 'dbHandleFactory' => $this->getDBFactory()
97 ] );
98 }
99
100 foreach (
101 [
102 'useJsonMetadata',
103 'useSplitMetadata',
104 'splitMetadataThreshold',
105 'updateCompatibleMetadata',
106 'reserializeMetadata',
107 ] as $option
108 ) {
109 if ( isset( $info[$option] ) ) {
110 $this->$option = $info[$option];
111 }
112 }
113 }
114
119 public function newFileFromRow( $row ) {
120 if ( isset( $row->img_name ) ) {
121 return ( $this->fileFromRowFactory )( $row, $this );
122 } elseif ( isset( $row->oi_name ) ) {
123 return ( $this->oldFileFromRowFactory )( $row, $this );
124 } else {
125 throw new InvalidArgumentException( __METHOD__ . ': invalid row' );
126 }
127 }
128
134 public function newFromArchiveName( $title, $archiveName ) {
135 $title = File::normalizeTitle( $title );
136 return OldLocalFile::newFromArchiveName( $title, $this, $archiveName );
137 }
138
149 public function cleanupDeletedBatch( array $storageKeys ) {
150 if ( $this->hasSha1Storage() ) {
151 wfDebug( __METHOD__ . ": skipped because storage uses sha1 paths" );
152 return Status::newGood();
153 }
154
155 $backend = $this->backend; // convenience
156 $root = $this->getZonePath( 'deleted' );
157 $dbw = $this->getPrimaryDB();
158 $status = $this->newGood();
159 $storageKeys = array_unique( $storageKeys );
160 foreach ( $storageKeys as $key ) {
161 $hashPath = $this->getDeletedHashPath( $key );
162 $path = "$root/$hashPath$key";
163 $dbw->startAtomic( __METHOD__ );
164 // Check for usage in deleted/hidden files and preemptively
165 // lock the key to avoid any future use until we are finished.
166 $deleted = $this->deletedFileHasKey( $key, 'lock' );
167 $hidden = $this->hiddenFileHasKey( $key, 'lock' );
168 if ( !$deleted && !$hidden ) { // not in use now
169 wfDebug( __METHOD__ . ": deleting $key" );
170 $op = [ 'op' => 'delete', 'src' => $path ];
171 if ( !$backend->doOperation( $op )->isOK() ) {
172 $status->error( 'undelete-cleanup-error', $path );
173 $status->failCount++;
174 }
175 } else {
176 wfDebug( __METHOD__ . ": $key still in use" );
177 $status->successCount++;
178 }
179 $dbw->endAtomic( __METHOD__ );
180 }
181
182 return $status;
183 }
184
192 protected function deletedFileHasKey( $key, $lock = null ) {
193 $queryBuilder = $this->getPrimaryDB()->newSelectQueryBuilder()
194 ->select( '1' )
195 ->from( 'filearchive' )
196 ->where( [ 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ] );
197 if ( $lock === 'lock' ) {
198 $queryBuilder->forUpdate();
199 }
200 return (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
201 }
202
210 protected function hiddenFileHasKey( $key, $lock = null ) {
211 $sha1 = self::getHashFromKey( $key );
212 $ext = File::normalizeExtension( substr( $key, strcspn( $key, '.' ) + 1 ) );
213
214 $dbw = $this->getPrimaryDB();
215 $queryBuilder = $dbw->newSelectQueryBuilder()
216 ->select( '1' )
217 ->from( 'oldimage' )
218 ->where( [
219 'oi_sha1' => $sha1,
220 $dbw->expr( 'oi_archive_name', IExpression::LIKE, new LikeValue( $dbw->anyString(), ".$ext" ) ),
221 $dbw->bitAnd( 'oi_deleted', File::DELETED_FILE ) => File::DELETED_FILE,
222 ] );
223 if ( $lock === 'lock' ) {
224 $queryBuilder->forUpdate();
225 }
226
227 return (bool)$queryBuilder->caller( __METHOD__ )->fetchField();
228 }
229
236 public static function getHashFromKey( $key ) {
237 $sha1 = strtok( $key, '.' );
238 if ( is_string( $sha1 ) && strlen( $sha1 ) === 32 && $sha1[0] === '0' ) {
239 $sha1 = substr( $sha1, 1 );
240 }
241 return $sha1;
242 }
243
250 public function checkRedirect( $title ) {
251 $title = File::normalizeTitle( $title, 'exception' );
252
253 $memcKey = $this->getSharedCacheKey( 'file-redirect', md5( $title->getDBkey() ) );
254 if ( $memcKey === false ) {
255 $memcKey = $this->getLocalCacheKey( 'file-redirect', md5( $title->getDBkey() ) );
256 $expiry = 300; // no invalidation, 5 minutes
257 } else {
258 $expiry = 86400; // has invalidation, 1 day
259 }
260
261 $method = __METHOD__;
262 $redirDbKey = $this->wanCache->getWithSetCallback(
263 $memcKey,
264 $expiry,
265 function ( $oldValue, &$ttl, array &$setOpts ) use ( $method, $title ) {
266 $dbr = $this->getReplicaDB(); // possibly remote DB
267
268 $setOpts += Database::getCacheSetOptions( $dbr );
269
270 $row = $dbr->newSelectQueryBuilder()
271 ->select( [ 'rd_namespace', 'rd_title' ] )
272 ->from( 'page' )
273 ->join( 'redirect', null, 'rd_from = page_id' )
274 ->where( [ 'page_namespace' => $title->getNamespace(), 'page_title' => $title->getDBkey() ] )
275 ->caller( $method )->fetchRow();
276
277 return ( $row && $row->rd_namespace == NS_FILE )
278 ? Title::makeTitle( $row->rd_namespace, $row->rd_title )->getDBkey()
279 : ''; // negative cache
280 },
281 [ 'pcTTL' => WANObjectCache::TTL_PROC_LONG ]
282 );
283
284 // @note: also checks " " for b/c
285 if ( $redirDbKey !== ' ' && strval( $redirDbKey ) !== '' ) {
286 // Page is a redirect to another file
287 return Title::newFromText( $redirDbKey, NS_FILE );
288 }
289
290 return false; // no redirect
291 }
292
294 public function findFiles( array $items, $flags = 0 ) {
295 $finalFiles = []; // map of (DB key => corresponding File) for matches
296
297 $searchSet = []; // map of (normalized DB key => search params)
298 foreach ( $items as $item ) {
299 if ( is_array( $item ) ) {
300 $title = File::normalizeTitle( $item['title'] );
301 if ( $title ) {
302 $searchSet[$title->getDBkey()] = $item;
303 }
304 } else {
305 $title = File::normalizeTitle( $item );
306 if ( $title ) {
307 $searchSet[$title->getDBkey()] = [];
308 }
309 }
310 }
311
312 $fileMatchesSearch = static function ( File $file, array $search ) {
313 // Note: file name comparison done elsewhere (to handle redirects)
314
315 // Fallback to RequestContext::getMain should be replaced with a better
316 // way of setting the user that should be used; currently it needs to be
317 // set for each file individually. See T263033#6477586
318 $contextPerformer = RequestContext::getMain()->getAuthority();
319 $performer = ( !empty( $search['private'] ) && $search['private'] instanceof Authority )
320 ? $search['private']
321 : $contextPerformer;
322
323 return (
324 $file->exists() &&
325 (
326 ( empty( $search['time'] ) && !$file->isOld() ) ||
327 ( !empty( $search['time'] ) && $search['time'] === $file->getTimestamp() )
328 ) &&
329 ( !empty( $search['private'] ) || !$file->isDeleted( File::DELETED_FILE ) ) &&
330 $file->userCan( File::DELETED_FILE, $performer )
331 );
332 };
333
334 $applyMatchingFiles = function ( IResultWrapper $res, &$searchSet, &$finalFiles )
335 use ( $fileMatchesSearch, $flags )
336 {
337 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
338 $info = $this->getInfo();
339 foreach ( $res as $row ) {
340 $file = $this->newFileFromRow( $row );
341 // There must have been a search for this DB key, but this has to handle the
342 // cases were title capitalization is different on the client and repo wikis.
343 $dbKeysLook = [ strtr( $file->getName(), ' ', '_' ) ];
344 if ( !empty( $info['initialCapital'] ) ) {
345 // Search keys for "hi.png" and "Hi.png" should use the "Hi.png file"
346 $dbKeysLook[] = $contLang->lcfirst( $file->getName() );
347 }
348 foreach ( $dbKeysLook as $dbKey ) {
349 if ( isset( $searchSet[$dbKey] )
350 && $fileMatchesSearch( $file, $searchSet[$dbKey] )
351 ) {
352 $finalFiles[$dbKey] = ( $flags & FileRepo::NAME_AND_TIME_ONLY )
353 ? [ 'title' => $dbKey, 'timestamp' => $file->getTimestamp() ]
354 : $file;
355 unset( $searchSet[$dbKey] );
356 }
357 }
358 }
359 };
360
361 $dbr = $this->getReplicaDB();
362
363 // Query image table
364 $imgNames = [];
365 foreach ( $searchSet as $dbKey => $_ ) {
366 $imgNames[] = $this->getNameFromTitle( File::normalizeTitle( $dbKey ) );
367 }
368
369 if ( count( $imgNames ) ) {
370 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
371 $res = $queryBuilder->where( [ 'img_name' => $imgNames ] )->caller( __METHOD__ )->fetchResultSet();
372 $applyMatchingFiles( $res, $searchSet, $finalFiles );
373 }
374
375 // Query old image table
376 $oiConds = []; // WHERE clause array for each file
377 foreach ( $searchSet as $dbKey => $search ) {
378 if ( isset( $search['time'] ) ) {
379 $oiConds[] = $dbr
380 ->expr( 'oi_name', '=', $this->getNameFromTitle( File::normalizeTitle( $dbKey ) ) )
381 ->and( 'oi_timestamp', '=', $dbr->timestamp( $search['time'] ) );
382 }
383 }
384
385 if ( count( $oiConds ) ) {
386 $queryBuilder = FileSelectQueryBuilder::newForOldFile( $dbr );
387
388 $res = $queryBuilder->where( $dbr->orExpr( $oiConds ) )
389 ->caller( __METHOD__ )->fetchResultSet();
390 $applyMatchingFiles( $res, $searchSet, $finalFiles );
391 }
392
393 // Check for redirects...
394 foreach ( $searchSet as $dbKey => $search ) {
395 if ( !empty( $search['ignoreRedirect'] ) ) {
396 continue;
397 }
398
399 $title = File::normalizeTitle( $dbKey );
400 $redir = $this->checkRedirect( $title ); // hopefully hits memcached
401
402 if ( $redir && $redir->getNamespace() === NS_FILE ) {
403 $file = $this->newFile( $redir );
404 if ( $file && $fileMatchesSearch( $file, $search ) ) {
405 $file->redirectedFrom( $title->getDBkey() );
406 if ( $flags & FileRepo::NAME_AND_TIME_ONLY ) {
407 $finalFiles[$dbKey] = [
408 'title' => $file->getTitle()->getDBkey(),
409 'timestamp' => $file->getTimestamp()
410 ];
411 } else {
412 $finalFiles[$dbKey] = $file;
413 }
414 }
415 }
416 }
417
418 return $finalFiles;
419 }
420
428 public function findBySha1( $hash ) {
429 $queryBuilder = FileSelectQueryBuilder::newForFile( $this->getReplicaDB() );
430 $res = $queryBuilder->where( [ 'img_sha1' => $hash ] )
431 ->orderBy( 'img_name' )
432 ->caller( __METHOD__ )->fetchResultSet();
433
434 $result = [];
435 foreach ( $res as $row ) {
436 $result[] = $this->newFileFromRow( $row );
437 }
438 $res->free();
439
440 return $result;
441 }
442
452 public function findBySha1s( array $hashes ) {
453 if ( $hashes === [] ) {
454 return []; // empty parameter
455 }
456
457 $dbr = $this->getReplicaDB();
458 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
459
460 $queryBuilder->where( [ 'img_sha1' => $hashes ] )
461 ->orderBy( 'img_name' );
462 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
463
464 $result = [];
465 foreach ( $res as $row ) {
466 $file = $this->newFileFromRow( $row );
467 $result[$file->getSha1()][] = $file;
468 }
469 $res->free();
470
471 return $result;
472 }
473
481 public function findFilesByPrefix( $prefix, $limit ) {
482 $dbr = $this->getReplicaDB();
483 $queryBuilder = FileSelectQueryBuilder::newForFile( $dbr );
484
485 $queryBuilder
486 ->where( $dbr->expr( 'img_name', IExpression::LIKE, new LikeValue( $prefix, $dbr->anyString() ) ) )
487 ->orderBy( 'img_name' )
488 ->limit( intval( $limit ) );
489 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
490
491 // Build file objects
492 $files = [];
493 foreach ( $res as $row ) {
494 $files[] = $this->newFileFromRow( $row );
495 }
496
497 return $files;
498 }
499
504 public function getReplicaDB() {
505 return $this->dbProvider->getReplicaDatabase();
506 }
507
513 public function getPrimaryDB() {
514 return $this->dbProvider->getPrimaryDatabase();
515 }
516
521 protected function getDBFactory() {
522 // TODO: DB_REPLICA/DB_PRIMARY shouldn't be passed around
523 return static function ( $index ) {
524 if ( $index === DB_PRIMARY ) {
525 return MediaWikiServices::getInstance()->getConnectionProvider()->getPrimaryDatabase();
526 } else {
527 return MediaWikiServices::getInstance()->getConnectionProvider()->getReplicaDatabase();
528 }
529 };
530 }
531
538 protected function hasAcessibleSharedCache() {
540 }
541
543 public function getSharedCacheKey( $kClassSuffix, ...$components ) {
544 // T267668: do not include the repo name in the key
545 return $this->hasAcessibleSharedCache()
546 ? $this->wanCache->makeGlobalKey(
547 'filerepo-' . $kClassSuffix,
548 $this->dbDomain,
549 ...$components
550 )
551 : false;
552 }
553
560 public function invalidateImageRedirect( $title ) {
561 $key = $this->getSharedCacheKey( 'file-redirect', md5( $title->getDBkey() ) );
562 if ( $key ) {
563 $this->getPrimaryDB()->onTransactionPreCommitOrIdle(
564 function () use ( $key ) {
565 $this->wanCache->delete( $key );
566 },
567 __METHOD__
568 );
569 }
570 }
571
573 public function store( $srcPath, $dstZone, $dstRel, $flags = 0 ) {
574 return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
575 }
576
578 public function storeBatch( array $triplets, $flags = 0 ) {
579 return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
580 }
581
583 public function cleanupBatch( array $files, $flags = 0 ) {
584 return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
585 }
586
588 public function publish(
589 $src,
590 $dstRel,
591 $archiveRel,
592 $flags = 0,
593 array $options = []
594 ) {
595 return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
596 }
597
599 public function publishBatch( array $ntuples, $flags = 0 ) {
600 return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
601 }
602
604 public function delete( $srcRel, $archiveRel ) {
605 return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
606 }
607
609 public function deleteBatch( array $sourceDestPairs ) {
610 return $this->skipWriteOperationIfSha1( __FUNCTION__, func_get_args() );
611 }
612
620 protected function skipWriteOperationIfSha1( $function, array $args ) {
621 $this->assertWritableRepo(); // fail out if read-only
622
623 if ( $this->hasSha1Storage() ) {
624 wfDebug( __METHOD__ . ": skipped because storage uses sha1 paths" );
625 return Status::newGood();
626 } else {
627 return parent::$function( ...$args );
628 }
629 }
630
640 public function isJsonMetadataEnabled() {
642 }
643
650 public function isSplitMetadataEnabled() {
652 }
653
660 public function getSplitMetadataThreshold() {
662 }
663
665 public function isMetadataUpdateEnabled() {
667 }
668
672 }
673
678 public function getBlobStore(): ?BlobStore {
679 if ( !$this->blobStore ) {
680 $this->blobStore = MediaWikiServices::getInstance()->getBlobStoreFactory()
681 ->newBlobStore( $this->dbDomain );
682 }
683 return $this->blobStore;
684 }
685
692 public function getUploadStash( ?UserIdentity $user = null ) {
693 return new UploadStash( $this, $user );
694 }
695}
696
698class_alias( LocalRepo::class, 'LocalRepo' );
const NS_FILE
Definition Defines.php:57
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
const DB_PRIMARY
Definition defines.php:28
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
Group all the pieces relevant to the context of a request into one instance.
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
Proxy backend that manages file layout rewriting for FileRepo.
Base class for file repositories.
Definition FileRepo.php:51
getNameFromTitle( $title)
Get the name of a file from its title.
Definition FileRepo.php:715
getDeletedHashPath( $key)
Get a relative path for a deletion archive key, e.g.
newGood( $value=null)
Create a new good result.
getInfo()
Return information about the repository.
newFile( $title, $time=false)
Create a new File object from the local repository.
Definition FileRepo.php:421
assertWritableRepo()
Throw an exception if this repo is read-only by design.
getLocalCacheKey( $kClassSuffix,... $components)
Get a site-local, repository-qualified, WAN cache key.
getZonePath( $zone)
Get the storage path corresponding to one of the zones.
Definition FileRepo.php:397
hasSha1Storage()
Returns whether or not storage is SHA-1 based.
Implements some public methods and some protected utility functions which are required by multiple ch...
Definition File.php:79
isDeleted( $field)
Is this file a "deleted" file in a private archive? STUB.
Definition File.php:2086
exists()
Returns true if file exists in the repository.
Definition File.php:1036
getTitle()
Return the associated title object.
Definition File.php:377
getTimestamp()
Get the 14-character timestamp of the file upload.
Definition File.php:2320
userCan( $field, Authority $performer)
Determine if the current user is allowed to view a particular field of this file, if it's marked as d...
Definition File.php:2375
isOld()
Returns true if the image is an old version STUB.
Definition File.php:2074
getName()
Return the name of this file.
Definition File.php:347
redirectedFrom(string $from)
Definition File.php:2469
Local file in the wiki's own database.
Definition LocalFile.php:81
Old file in the oldimage table.
Local repository that stores files in the local filesystem and registers them in the wiki's own datab...
Definition LocalRepo.php:45
getSplitMetadataThreshold()
Get the threshold above which metadata items should be split into separate storage,...
findFilesByPrefix( $prefix, $limit)
Return an array of files where the name starts with $prefix.
deleteBatch(array $sourceDestPairs)
Move a group of files to the deletion archive.If no valid deletion archive is configured,...
cleanupBatch(array $files, $flags=0)
Deletes a batch of files.Each file can be a (zone, rel) pair, virtual url, storage path....
invalidateImageRedirect( $title)
Invalidates image redirect cache related to that image.
findFiles(array $items, $flags=0)
Find many files at once.array Map of (file name => File objects) for matches or (search title => (tit...
store( $srcPath, $dstZone, $dstRel, $flags=0)
Store a file to a given destination.Using FSFile/TempFSFile can improve performance via caching....
getPrimaryDB()
Get a connection to the primary DB.
cleanupDeletedBatch(array $storageKeys)
Delete files in the deleted directory if they are not referenced in the filearchive table.
checkRedirect( $title)
Checks if there is a redirect named as $title.
hiddenFileHasKey( $key, $lock=null)
Check if a hidden (revision delete) file has this sha1 key.
skipWriteOperationIfSha1( $function, array $args)
Skips the write operation if storage is sha1-based, executes it normally otherwise.
getSharedCacheKey( $kClassSuffix,... $components)
Get a global, repository-qualified, WAN cache key.This might be called from either the site context o...
getDBFactory()
Get a callback to get a DB handle given an index (DB_REPLICA/DB_PRIMARY)
publishBatch(array $ntuples, $flags=0)
Publish a batch of files.FileRepo::publish()Status
getBlobStore()
Get a BlobStore for storing and retrieving large metadata, or null if that can't be done.
__construct(?array $info=null)
Definition LocalRepo.php:83
storeBatch(array $triplets, $flags=0)
Store a batch of files.FileRepo::store()Status
newFromArchiveName( $title, $archiveName)
deletedFileHasKey( $key, $lock=null)
Check if a deleted (filearchive) file has this sha1 key.
string $dbDomain
DB domain of the repo wiki.
Definition LocalRepo.php:60
IConnectionProvider $dbProvider
Definition LocalRepo.php:61
getUploadStash(?UserIdentity $user=null)
Get an UploadStash associated with this repo.
getReplicaDB()
Get a connection to the replica DB.
findBySha1( $hash)
Get an array or iterator of file objects for files that have a given SHA-1 content hash.
findBySha1s(array $hashes)
Get an array of arrays or iterators of file objects for files that have the given SHA-1 content hashe...
static getHashFromKey( $key)
Gets the SHA1 hash from a storage key.
hasAcessibleSharedCache()
Check whether the repo has a shared cache, accessible from the current site context.
publish( $src, $dstRel, $archiveRel, $flags=0, array $options=[])
Copy or move a file either from a storage path, virtual URL, or file system path, into this repositor...
isSplitMetadataEnabled()
Returns true if files should split up large metadata, storing parts of it in the BlobStore.
bool $hasAccessibleSharedCache
Whether shared cache keys are exposed/accessible.
Definition LocalRepo.php:63
isJsonMetadataEnabled()
Returns true if files should store metadata in JSON format.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition Status.php:44
Represents a title within MediaWiki.
Definition Title.php:69
UploadStash is intended to accomplish a few things:
Tools for dealing with other locally-hosted wikis.
Definition WikiMap.php:19
doOperation(array $op, array $opts=[])
Same as doOperations() except it takes a single operation.
Multi-datacenter aware caching interface.
static getCacheSetOptions(?IReadableDatabase ... $dbs)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Content of like value.
Definition LikeValue.php:14
Represents the target of a wiki link.
Interface for objects (potentially) representing an editable wiki page.
This interface represents the authority associated with the current execution context,...
Definition Authority.php:23
Service for loading and storing data blobs.
Definition BlobStore.php:19
Interface for objects representing user identity.
Provide primary and replica IDatabase connections.
Interface to a relational database.
Definition IDatabase.php:31
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.