53 $this->loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
62 protected function initialize( $mode = self::LOAD_ONLY ) {
63 if ( $this->mName ===
null && $this->mID ===
null ) {
64 throw new MWException( __METHOD__ .
' has both names and IDs null' );
65 } elseif ( $this->mID ===
null ) {
66 $where = [
'cat_title' => $this->mName ];
67 } elseif ( $this->mName ===
null ) {
68 $where = [
'cat_id' => $this->mID ];
75 $row =
$dbr->selectRow(
77 [
'cat_id',
'cat_title',
'cat_pages',
'cat_subcats',
'cat_files' ],
83 # Okay, there were no contents. Nothing to initialize.
84 if ( $this->mTitle ) {
85 # If there is a title object but no record in the category table,
86 # treat this as an empty category.
88 $this->mName = $this->mTitle->getDBkey();
93 # If the title exists, call refreshCounts to add a row for it.
94 if ( $mode === self::LAZY_INIT_ROW && $this->mTitle->exists() ) {
95 DeferredUpdates::addCallableUpdate( [ $this,
'refreshCounts' ] );
104 $this->mID = $row->cat_id;
105 $this->mName = $row->cat_title;
106 $this->mPages = $row->cat_pages;
107 $this->mSubcats = $row->cat_subcats;
108 $this->mFiles = $row->cat_files;
110 # (T15683) If the count is negative, then 1) it's obviously wrong
111 # and should not be kept, and 2) we *probably* don't have to scan many
112 # rows to obtain the correct figure, so let's risk a one-time recount.
113 if ( $this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0 ) {
114 $this->mPages = max( $this->mPages, 0 );
115 $this->mSubcats = max( $this->mSubcats, 0 );
116 $this->mFiles = max( $this->mFiles, 0 );
118 if ( $mode === self::LAZY_INIT_ROW ) {
119 DeferredUpdates::addCallableUpdate( [ $this,
'refreshCounts' ] );
137 if ( !is_object(
$title ) ) {
142 $cat->mName =
$title->getDBkey();
157 $cat->mName =
$title->getDBkey();
170 $cat->mID = intval( $id );
190 # NOTE: the row often results from a LEFT JOIN on categorylinks. This may result in
191 # all the cat_xxx fields being null, if the category page exists, but nothing
192 # was ever added to the category. This case should be treated link an empty
193 # category, if possible.
195 if ( $row->cat_title ===
null ) {
197 # the name is probably somewhere in the row, for example as page_title,
198 # but we can't know that here...
201 # if we have a title object, fetch the category name from there
202 $cat->mName =
$title->getDBkey();
210 $cat->mName = $row->cat_title;
211 $cat->mID = $row->cat_id;
212 $cat->mSubcats = $row->cat_subcats;
213 $cat->mPages = $row->cat_pages;
214 $cat->mFiles = $row->cat_files;
224 return $this->
getX(
'mName' );
231 return $this->
getX(
'mID' );
238 return $this->
getX(
'mPages' );
245 return $this->
getX(
'mSubcats' );
252 return $this->
getX(
'mFiles' );
259 if ( $this->mTitle ) {
263 if ( !$this->
initialize( self::LAZY_INIT_ROW ) ) {
267 $this->mTitle = Title::makeTitleSafe(
NS_CATEGORY, $this->mName );
281 $conds = [
'cl_to' => $this->
getName(),
'cl_from = page_id' ];
282 $options = [
'ORDER BY' =>
'cl_sortkey' ];
285 $options[
'LIMIT'] = $limit;
288 if ( $offset !==
'' ) {
289 $conds[] =
'cl_sortkey > ' .
$dbr->addQuotes( $offset );
292 $result = TitleArray::newFromResult(
294 [
'page',
'categorylinks' ],
295 [
'page_id',
'page_namespace',
'page_title',
'page_len',
296 'page_is_redirect',
'page_latest' ],
311 private function getX( $key ) {
312 if ( $this->{$key} ===
null && !$this->
initialize( self::LAZY_INIT_ROW ) ) {
315 return $this->{$key};
328 # If we have just a category name, find out whether there is an
329 # existing row. Or if we have just an ID, get the name, because
330 # that's what categorylinks uses.
331 if ( !$this->
initialize( self::LOAD_ONLY ) ) {
335 $dbw = $this->loadBalancer->getConnectionRef(
DB_MASTER );
336 # Avoid excess contention on the same category (T162121)
337 $name = __METHOD__ .
':' . md5( $this->mName );
338 $scopedLock = $dbw->getScopedLockAndFlush( $name, __METHOD__, 0 );
339 if ( !$scopedLock ) {
343 $dbw->startAtomic( __METHOD__ );
347 $dbw->lockForUpdate(
'category', [
'cat_title' => $this->mName ], __METHOD__ );
351 $dbw->selectRowCount(
352 [
'categorylinks',
'page' ],
354 [
'cl_to' => $this->mName,
'page_id = cl_from' ],
356 [
'LOCK IN SHARE MODE' ]
359 $catCond = $dbw->conditional( [
'page_namespace' =>
NS_CATEGORY ],
'1',
'NULL' );
360 $fileCond = $dbw->conditional( [
'page_namespace' =>
NS_FILE ],
'1',
'NULL' );
361 $result = $dbw->selectRow(
362 [
'categorylinks',
'page' ],
364 'pages' =>
'COUNT(*)',
365 'subcats' =>
"COUNT($catCond)",
366 'files' =>
"COUNT($fileCond)"
368 [
'cl_to' => $this->mName,
'page_id = cl_from' ],
372 $shouldExist = $result->pages > 0 || $this->
getTitle()->exists();
375 if ( $shouldExist ) {
376 # The category row already exists, so do a plain UPDATE instead
377 # of INSERT...ON DUPLICATE KEY UPDATE to avoid creating a gap
378 # in the cat_id sequence. The row may or may not be "affected".
382 'cat_pages' => $result->pages,
383 'cat_subcats' => $result->subcats,
384 'cat_files' => $result->files
386 [
'cat_title' => $this->mName ],
390 # The category is empty and has no description page, delete it
393 [
'cat_title' => $this->mName ],
398 } elseif ( $shouldExist ) {
399 # The category row doesn't exist but should, so create it. Use
400 # upsert in case of races.
404 'cat_title' => $this->mName,
405 'cat_pages' => $result->pages,
406 'cat_subcats' => $result->subcats,
407 'cat_files' => $result->files
411 'cat_pages' => $result->pages,
412 'cat_subcats' => $result->subcats,
413 'cat_files' => $result->files
421 $dbw->endAtomic( __METHOD__ );
423 # Now we should update our local counts.
424 $this->mPages = $result->pages;
425 $this->mSubcats = $result->subcats;
426 $this->mFiles = $result->files;
460 $dbw = $this->loadBalancer->getConnectionRef(
DB_MASTER );
461 $dbw->startAtomic( __METHOD__ );
463 $typeOccurances = $dbw->selectFieldValues(
466 [
'cl_to' => $this->
getName() ],
468 [
'LIMIT' => $maxSize + 1 ]
471 if ( !$typeOccurances ) {
473 } elseif ( count( $typeOccurances ) <= $maxSize ) {
474 $countByType = array_count_values( $typeOccurances );
475 $doRefresh = !$dbw->selectField(
479 'cat_title' => $this->
getName(),
480 'cat_pages' => $countByType[
'page'] ?? 0,
481 'cat_subcats' => $countByType[
'subcat'] ?? 0,
482 'cat_files' => $countByType[
'file'] ?? 0
490 $dbw->endAtomic( __METHOD__ );
wfReadOnly()
Check whether the wiki is in read-only mode.
Category objects are immutable, strictly speaking.
static newFromID( $id)
Factory function.
static newFromName( $name)
Factory function.
refreshCountsIfEmpty()
Call refreshCounts() if there are no entries in the categorylinks table or if the category table has ...
$mName
Name of the category, normalized to DB-key form.
static newFromTitle( $title)
Factory function.
initialize( $mode=self::LOAD_ONLY)
Set up all member variables using a database query.
$mPages
Counts of membership (cat_pages, cat_subcats, cat_files)
getMembers( $limit=false, $offset='')
Fetch a TitleArray of up to $limit category members, beginning after the category sort key $offset.
refreshCounts()
Refresh the counts for this category.
getX( $key)
Generic accessor.
refreshCountsIfSmall( $maxSize=self::ROW_COUNT_SMALL)
Call refreshCounts() if there are few entries in the categorylinks table.
ILoadBalancer $loadBalancer
static newFromRow( $row, $title=null)
Factory function, for constructing a Category object from a result set.
Represents a title within MediaWiki.