Translate extension for MediaWiki
 
Loading...
Searching...
No Matches
MessageIndex.php
Go to the documentation of this file.
1<?php
11use Cdb\Reader;
12use Cdb\Writer;
16use MediaWiki\Logger\LoggerFactory;
17use MediaWiki\MediaWikiServices;
18
27abstract class MessageIndex {
28 private const CACHEKEY = 'Translate-MessageIndex-interim';
29
30 private const READ_LATEST = true;
31
33 protected static $instance;
35 private static $keysCache;
37 protected $interimCache;
39 private $statusCache;
41 private $jobQueueGroup;
42
43 public function __construct() {
44 // TODO: Use dependency injection
45 $mwInstance = MediaWikiServices::getInstance();
46 $this->statusCache = $mwInstance->getMainWANObjectCache();
47 $this->jobQueueGroup = $mwInstance->getJobQueueGroup();
48 }
49
54 public static function singleton(): self {
55 if ( self::$instance === null ) {
56 self::$instance = Services::getInstance()->getMessageIndex();
57 }
58
59 return self::$instance;
60 }
61
68 public static function setInstance( self $instance ) {
69 self::$instance = $instance;
70 }
71
78 public static function getGroupIds( MessageHandle $handle ): array {
79 global $wgTranslateMessageNamespaces;
80
81 $title = $handle->getTitle();
82
83 if ( !$title->inNamespaces( $wgTranslateMessageNamespaces ) ) {
84 return [];
85 }
86
87 $namespace = $title->getNamespace();
88 $key = $handle->getKey();
89 $normkey = Utilities::normaliseKey( $namespace, $key );
90
91 $cache = self::getCache();
92 $value = $cache->get( $normkey );
93 if ( $value === null ) {
94 $value = (array)self::singleton()->getWithCache( $normkey );
95 $cache->set( $normkey, $value );
96 }
97
98 return $value;
99 }
100
102 private static function getCache() {
103 if ( self::$keysCache === null ) {
104 self::$keysCache = new MapCacheLRU( 30 );
105 }
106 return self::$keysCache;
107 }
108
114 public static function getPrimaryGroupId( MessageHandle $handle ): ?string {
115 $groups = self::getGroupIds( $handle );
116
117 return count( $groups ) ? array_shift( $groups ) : null;
118 }
119
120 private function getWithCache( $key ) {
121 $interimCacheValue = $this->getInterimCache()->get( self::CACHEKEY );
122 if ( $interimCacheValue && isset( $interimCacheValue['newKeys'][$key] ) ) {
123 return $interimCacheValue['newKeys'][$key];
124 }
125
126 return $this->get( $key );
127 }
128
135 protected function get( $key ) {
136 // Default implementation
137 $mi = $this->retrieve();
138 return $mi[$key] ?? null;
139 }
140
141 abstract public function retrieve( bool $readLatest = false ): array;
142
147 public function getKeys() {
148 return array_keys( $this->retrieve() );
149 }
150
151 abstract protected function store( array $array, array $diff );
152
153 protected function lock() {
154 return true;
155 }
156
157 protected function unlock() {
158 return true;
159 }
160
168 public function rebuild( float $timestamp = null ): array {
169 $logger = LoggerFactory::getInstance( 'Translate' );
170
171 static $recursion = 0;
172
173 if ( $recursion > 0 ) {
174 $msg = __METHOD__ . ': trying to recurse - building the index first time?';
175 wfWarn( $msg );
176
177 $recursion--;
178 return [];
179 }
180 $recursion++;
181
182 $logger->info(
183 '[MessageIndex] Started rebuild. Initiated by {callers}',
184 [ 'callers' => wfGetAllCallers( 20 ) ]
185 );
186
187 $groups = MessageGroups::singleton()->getGroups();
188
189 $tsStart = microtime( true );
190 if ( !$this->lock() ) {
191 throw new MessageIndexException( __CLASS__ . ': unable to acquire lock' );
192 }
193
194 $lockWaitDuration = microtime( true ) - $tsStart;
195 $logger->info(
196 '[MessageIndex] Got lock in {duration}',
197 [ 'duration' => $lockWaitDuration ]
198 );
199
200 self::getCache()->clear();
201
202 $new = [];
203 $old = $this->retrieve( self::READ_LATEST );
204 $postponed = [];
205
207 foreach ( $groups as $g ) {
208 if ( !$g->exists() ) {
209 $id = $g->getId();
210 wfWarn( __METHOD__ . ": group '$id' is registered but does not exist" );
211 continue;
212 }
213
214 # Skip meta thingies
215 if ( $g->isMeta() ) {
216 $postponed[] = $g;
217 continue;
218 }
219
220 $this->checkAndAdd( $new, $g );
221 }
222
223 foreach ( $postponed as $g ) {
224 $this->checkAndAdd( $new, $g, true );
225 }
226
227 $diff = self::getArrayDiff( $old, $new );
228 $this->store( $new, $diff['keys'] );
229 $this->unlock();
230
231 $criticalSectionDuration = microtime( true ) - $tsStart - $lockWaitDuration;
232 $logger->info(
233 '[MessageIndex] Finished critical section in {duration}',
234 [ 'duration' => $criticalSectionDuration ]
235 );
236
237 $cache = $this->getInterimCache();
238 $interimCacheValue = $cache->get( self::CACHEKEY );
239 $timestamp = $timestamp ?? microtime( true );
240 if ( $interimCacheValue ) {
241 if ( $interimCacheValue['timestamp'] <= $timestamp ) {
242 $cache->delete( self::CACHEKEY );
243 } else {
244 // Cache has a later timestamp. This may be caused due to
245 // job deduplication. Just in case, spin off a new job to clean up the cache.
247 $this->jobQueueGroup->push( $job );
248 }
249 }
250
251 // Other caches can check this key to know when they need to refresh
252 $this->statusCache->touchCheckKey( $this->getStatusCacheKey() );
253
254 $this->clearMessageGroupStats( $diff );
255
256 $recursion--;
257
258 return $new;
259 }
260
265 public function getStatusCacheKey(): string {
266 return $this->statusCache->makeKey( 'Translate', 'MessageIndex', 'status' );
267 }
268
269 private function getInterimCache(): BagOStuff {
270 return ObjectCache::getInstance( CACHE_ANYTHING );
271 }
272
273 public function storeInterim( MessageGroup $group, array $newKeys ): void {
274 $namespace = $group->getNamespace();
275 $id = $group->getId();
276
277 $normalizedNewKeys = [];
278 foreach ( $newKeys as $key ) {
279 $normalizedNewKeys[Utilities::normaliseKey( $namespace, $key )] = $id;
280 }
281
282 $cache = $this->getInterimCache();
283 // Merge existing with existing keys
284 $interimCacheValue = $cache->get( self::CACHEKEY, $cache::READ_LATEST );
285 if ( $interimCacheValue ) {
286 $normalizedNewKeys = array_merge( $interimCacheValue['newKeys'], $normalizedNewKeys );
287 }
288
289 $value = [
290 'timestamp' => microtime( true ),
291 'newKeys' => $normalizedNewKeys,
292 ];
293
294 $cache->set( self::CACHEKEY, $value, $cache::TTL_DAY );
295 }
296
325 public static function getArrayDiff( array $old, array $new ) {
326 $values = [];
327 $record = static function ( $groups ) use ( &$values ) {
328 foreach ( $groups as $group ) {
329 $values[$group] = true;
330 }
331 };
332
333 $keys = [
334 'add' => [],
335 'del' => [],
336 'mod' => [],
337 ];
338
339 foreach ( $new as $key => $groups ) {
340 if ( !isset( $old[$key] ) ) {
341 $keys['add'][$key] = [ [], (array)$groups ];
342 $record( (array)$groups );
343 // Using != here on purpose to ignore the order of items
344 } elseif ( $groups != $old[$key] ) {
345 $keys['mod'][$key] = [ (array)$old[$key], (array)$groups ];
346 $record( array_diff( (array)$old[$key], (array)$groups ) );
347 $record( array_diff( (array)$groups, (array)$old[$key] ) );
348 }
349 }
350
351 foreach ( $old as $key => $groups ) {
352 if ( !isset( $new[$key] ) ) {
353 $keys['del'][$key] = [ (array)$groups, [] ];
354 $record( (array)$groups );
355 }
356 // We already checked for diffs above
357 }
358
359 return [
360 'keys' => $keys,
361 'values' => array_keys( $values ),
362 ];
363 }
364
370 protected function clearMessageGroupStats( array $diff ) {
371 $job = MessageGroupStatsRebuildJob::newRefreshGroupsJob( $diff['values'] );
372 $this->jobQueueGroup->push( $job );
373
374 foreach ( $diff['keys'] as $keys ) {
375 foreach ( $keys as $key => $data ) {
376 [ $ns, $pagename ] = explode( ':', $key, 2 );
377 $title = Title::makeTitle( (int)$ns, $pagename );
378 $handle = new MessageHandle( $title );
379 [ $oldGroups, $newGroups ] = $data;
380 Hooks::run( 'TranslateEventMessageMembershipChange',
381 [ $handle, $oldGroups, $newGroups ] );
382 }
383 }
384 }
385
391 protected function checkAndAdd( &$hugearray, MessageGroup $g, $ignore = false ) {
392 $keys = $g->getKeys();
393 $id = $g->getId();
394 $namespace = $g->getNamespace();
395
396 foreach ( $keys as $key ) {
397 # Force all keys to lower case, because the case doesn't matter and it is
398 # easier to do comparing when the case of first letter is unknown, because
399 # mediawiki forces it to upper case
400 $key = Utilities::normaliseKey( $namespace, $key );
401 if ( isset( $hugearray[$key] ) ) {
402 if ( !$ignore ) {
403 $to = implode( ', ', (array)$hugearray[$key] );
404 wfWarn( "Key $key already belongs to $to, conflict with $id" );
405 }
406
407 if ( is_array( $hugearray[$key] ) ) {
408 // Hard work is already done, just add a new reference
409 $hugearray[$key][] = & $id;
410 } else {
411 // Store the actual reference, then remove it from array, to not
412 // replace the references value, but to store an array of new
413 // references instead. References are hard!
414 $value = & $hugearray[$key];
415 unset( $hugearray[$key] );
416 $hugearray[$key] = [ &$value, &$id ];
417 }
418 } else {
419 $hugearray[$key] = & $id;
420 }
421 }
422 unset( $id ); // Disconnect the previous references to this $id
423 }
424
432 protected function serialize( $data ) {
433 if ( is_array( $data ) ) {
434 return implode( '|', $data );
435 } else {
436 return $data;
437 }
438 }
439
440 protected function unserialize( $data ) {
441 if ( strpos( $data, '|' ) !== false ) {
442 return explode( '|', $data );
443 }
444
445 return $data;
446 }
447}
448
465 protected $index;
466 protected $filename = 'translate_messageindex.ser';
467
468 public function retrieve( bool $readLatest = false ): array {
469 if ( $this->index !== null ) {
470 return $this->index;
471 }
472
473 $file = Utilities::cacheFile( $this->filename );
474 if ( file_exists( $file ) ) {
475 $this->index = unserialize( file_get_contents( $file ) );
476 } else {
477 $this->index = $this->rebuild();
478 }
479
480 return $this->index;
481 }
482
483 protected function store( array $array, array $diff ) {
484 $file = Utilities::cacheFile( $this->filename );
485 file_put_contents( $file, serialize( $array ) );
486 $this->index = $array;
487 }
488}
489
503 protected $index;
504
505 protected function lock() {
506 $dbw = wfGetDB( DB_PRIMARY );
507
508 // Any transaction should be flushed after getting the lock to avoid
509 // stale pre-lock REPEATABLE-READ snapshot data.
510 $ok = $dbw->lock( 'translate-messageindex', __METHOD__, 30 );
511 if ( $ok ) {
512 $dbw->commit( __METHOD__, 'flush' );
513 }
514
515 return $ok;
516 }
517
518 protected function unlock() {
519 $fname = __METHOD__;
520 $dbw = wfGetDB( DB_PRIMARY );
521 // Unlock once the rows are actually unlocked to avoid deadlocks
522 if ( !$dbw->trxLevel() ) {
523 $dbw->unlock( 'translate-messageindex', $fname );
524 } elseif ( is_callable( [ $dbw, 'onTransactionResolution' ] ) ) { // 1.28
525 $dbw->onTransactionResolution( static function () use ( $dbw, $fname ) {
526 $dbw->unlock( 'translate-messageindex', $fname );
527 }, $fname );
528 } else {
529 $dbw->onTransactionCommitOrIdle( static function () use ( $dbw, $fname ) {
530 $dbw->unlock( 'translate-messageindex', $fname );
531 }, $fname );
532 }
533
534 return true;
535 }
536
537 public function retrieve( bool $readLatest = false ): array {
538 if ( $this->index !== null && !$readLatest ) {
539 return $this->index;
540 }
541
542 $dbr = wfGetDB( $readLatest ? DB_PRIMARY : DB_REPLICA );
543 $res = $dbr->select( 'translate_messageindex', '*', [], __METHOD__ );
544 $this->index = [];
545 foreach ( $res as $row ) {
546 $this->index[$row->tmi_key] = $this->unserialize( $row->tmi_value );
547 }
548
549 return $this->index;
550 }
551
552 protected function get( $key ) {
553 $dbr = wfGetDB( DB_REPLICA );
554 $value = $dbr->selectField(
555 'translate_messageindex',
556 'tmi_value',
557 [ 'tmi_key' => $key ],
558 __METHOD__
559 );
560
561 if ( is_string( $value ) ) {
562 $value = $this->unserialize( $value );
563 } else {
564 $value = null;
565 }
566
567 return $value;
568 }
569
570 protected function store( array $array, array $diff ) {
571 $updates = [];
572
573 foreach ( [ $diff['add'], $diff['mod'] ] as $changes ) {
574 foreach ( $changes as $key => $data ) {
575 [ , $new ] = $data;
576 $updates[] = [
577 'tmi_key' => $key,
578 'tmi_value' => $this->serialize( $new ),
579 ];
580 }
581 }
582
583 $index = [ 'tmi_key' ];
584 $deletions = array_keys( $diff['del'] );
585
586 $dbw = wfGetDB( DB_PRIMARY );
587 $dbw->startAtomic( __METHOD__ );
588
589 if ( $updates !== [] ) {
590 $dbw->replace( 'translate_messageindex', [ $index ], $updates, __METHOD__ );
591 }
592
593 if ( $deletions !== [] ) {
594 $dbw->delete( 'translate_messageindex', [ 'tmi_key' => $deletions ], __METHOD__ );
595 }
596
597 $dbw->endAtomic( __METHOD__ );
598
599 $this->index = $array;
600 }
601}
602
612 protected $key = 'translate-messageindex';
613 protected $cache;
615 protected $index;
616
617 protected function __construct() {
618 parent::__construct();
619 $this->cache = ObjectCache::getInstance( CACHE_ANYTHING );
620 }
621
622 public function retrieve( bool $readLatest = false ): array {
623 if ( $this->index !== null ) {
624 return $this->index;
625 }
626
627 $key = $this->cache->makeKey( $this->key );
628 $data = $this->cache->get( $key );
629 if ( is_array( $data ) ) {
630 $this->index = $data;
631 } else {
632 $this->index = $this->rebuild();
633 }
634
635 return $this->index;
636 }
637
638 protected function store( array $array, array $diff ) {
639 $key = $this->cache->makeKey( $this->key );
640 $this->cache->set( $key, $array );
641
642 $this->index = $array;
643 }
644}
645
661 protected $index;
663 protected $reader;
665 protected $filename = 'translate_messageindex.cdb';
666
671 public function retrieve( bool $readLatest = false ): array {
672 $reader = $this->getReader();
673 // This must be below the line above, which may fill the index
674 if ( $this->index !== null ) {
675 return $this->index;
676 }
677
678 $this->index = [];
679 foreach ( $this->getKeys() as $key ) {
680 $this->index[$key] = $this->unserialize( $reader->get( $key ) );
681 }
682
683 return $this->index;
684 }
685
686 public function getKeys() {
687 $reader = $this->getReader();
688 $keys = [];
689 $key = $reader->firstkey();
690 while ( $key !== false ) {
691 $keys[] = $key;
692 $key = $reader->nextkey();
693 }
694
695 return $keys;
696 }
697
698 protected function get( $key ) {
699 $reader = $this->getReader();
700 // We might have the full cache loaded
701 if ( $this->index !== null ) {
702 return $this->index[$key] ?? null;
703 }
704
705 $value = $reader->get( $key );
706 if ( !is_string( $value ) ) {
707 $value = null;
708 } else {
709 $value = $this->unserialize( $value );
710 }
711
712 return $value;
713 }
714
715 protected function store( array $array, array $diff ) {
716 $this->reader = null;
717
718 $file = Utilities::cacheFile( $this->filename );
719 $cache = Writer::open( $file );
720
721 foreach ( $array as $key => $value ) {
722 $value = $this->serialize( $value );
723 $cache->set( $key, $value );
724 }
725
726 $cache->close();
727
728 $this->index = $array;
729 }
730
731 protected function getReader() {
732 if ( $this->reader ) {
733 return $this->reader;
734 }
735
736 $file = Utilities::cacheFile( $this->filename );
737 if ( !file_exists( $file ) ) {
738 // Create an empty index to allow rebuild
739 $this->store( [], [] );
740 $this->index = $this->rebuild();
741 }
742
743 $this->reader = Reader::open( $file );
744 return $this->reader;
745 }
746}
747
757 protected $index = [];
758
759 public function retrieve( bool $readLatest = false ): array {
760 return $this->index;
761 }
762
767 protected function get( $key ) {
768 return $this->index[$key] ?? null;
769 }
770
771 protected function store( array $array, array $diff ) {
772 $this->index = $array;
773 }
774
775 protected function clearMessageGroupStats( array $diff ) {
776 }
777}
return[ 'Translate:ConfigHelper'=> static function():ConfigHelper { return new ConfigHelper();}, 'Translate:CsvTranslationImporter'=> static function(MediaWikiServices $services):CsvTranslationImporter { return new CsvTranslationImporter( $services->getWikiPageFactory());}, 'Translate:EntitySearch'=> static function(MediaWikiServices $services):EntitySearch { return new EntitySearch($services->getMainWANObjectCache(), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), MessageGroups::singleton(), $services->getNamespaceInfo(), $services->get( 'Translate:MessageIndex'), $services->getTitleParser(), $services->getTitleFormatter());}, 'Translate:ExternalMessageSourceStateImporter'=> static function(MediaWikiServices $services):ExternalMessageSourceStateImporter { return new ExternalMessageSourceStateImporter($services->getMainConfig(), $services->get( 'Translate:GroupSynchronizationCache'), $services->getJobQueueGroup(), LoggerFactory::getInstance( 'Translate.GroupSynchronization'), $services->get( 'Translate:MessageIndex'));}, 'Translate:GroupSynchronizationCache'=> static function(MediaWikiServices $services):GroupSynchronizationCache { return new GroupSynchronizationCache( $services->get( 'Translate:PersistentCache'));}, 'Translate:MessageBundleStore'=> static function(MediaWikiServices $services):MessageBundleStore { return new MessageBundleStore(new RevTagStore(), $services->getJobQueueGroup(), $services->getLanguageNameUtils(), $services->get( 'Translate:MessageIndex'));}, 'Translate:MessageGroupReview'=> static function(MediaWikiServices $services):MessageGroupReview { return new MessageGroupReview($services->getDBLoadBalancer(), $services->getHookContainer());}, 'Translate:MessageGroupStatsTableFactory'=> static function(MediaWikiServices $services):MessageGroupStatsTableFactory { return new MessageGroupStatsTableFactory($services->get( 'Translate:ProgressStatsTableFactory'), $services->getDBLoadBalancer(), $services->getLinkRenderer(), $services->getMainConfig() ->get( 'TranslateWorkflowStates') !==false);}, 'Translate:MessageIndex'=> static function(MediaWikiServices $services):MessageIndex { $params=$services->getMainConfig() ->get( 'TranslateMessageIndex');if(is_string( $params)) { $params=(array) $params;} $class=array_shift( $params);return new $class( $params);}, 'Translate:MessagePrefixStats'=> static function(MediaWikiServices $services):MessagePrefixStats { return new MessagePrefixStats( $services->getTitleParser());}, 'Translate:ParsingPlaceholderFactory'=> static function():ParsingPlaceholderFactory { return new ParsingPlaceholderFactory();}, 'Translate:PersistentCache'=> static function(MediaWikiServices $services):PersistentCache { return new PersistentDatabaseCache($services->getDBLoadBalancer(), $services->getJsonCodec());}, 'Translate:ProgressStatsTableFactory'=> static function(MediaWikiServices $services):ProgressStatsTableFactory { return new ProgressStatsTableFactory($services->getLinkRenderer(), $services->get( 'Translate:ConfigHelper'));}, 'Translate:SubpageListBuilder'=> static function(MediaWikiServices $services):SubpageListBuilder { return new SubpageListBuilder($services->get( 'Translate:TranslatableBundleFactory'), $services->getLinkBatchFactory());}, 'Translate:TranslatableBundleFactory'=> static function(MediaWikiServices $services):TranslatableBundleFactory { return new TranslatableBundleFactory($services->get( 'Translate:TranslatablePageStore'), $services->get( 'Translate:MessageBundleStore'));}, 'Translate:TranslatableBundleMover'=> static function(MediaWikiServices $services):TranslatableBundleMover { return new TranslatableBundleMover($services->getMovePageFactory(), $services->getJobQueueGroup(), $services->getLinkBatchFactory(), $services->get( 'Translate:TranslatableBundleFactory'), $services->get( 'Translate:SubpageListBuilder'), $services->getMainConfig() ->get( 'TranslatePageMoveLimit'));}, 'Translate:TranslatableBundleStatusStore'=> static function(MediaWikiServices $services):TranslatableBundleStatusStore { return new TranslatableBundleStatusStore($services->getDBLoadBalancer() ->getConnection(DB_PRIMARY), $services->getCollationFactory() ->makeCollation( 'uca-default-u-kn'), $services->getDBLoadBalancer() ->getMaintenanceConnectionRef(DB_PRIMARY));}, 'Translate:TranslatablePageParser'=> static function(MediaWikiServices $services):TranslatablePageParser { return new TranslatablePageParser($services->get( 'Translate:ParsingPlaceholderFactory'));}, 'Translate:TranslatablePageStore'=> static function(MediaWikiServices $services):TranslatablePageStore { return new TranslatablePageStore($services->get( 'Translate:MessageIndex'), $services->getJobQueueGroup(), new RevTagStore(), $services->getDBLoadBalancer(), $services->get( 'Translate:TranslatableBundleStatusStore'));}, 'Translate:TranslationStashReader'=> static function(MediaWikiServices $services):TranslationStashReader { $db=$services->getDBLoadBalancer() ->getConnectionRef(DB_REPLICA);return new TranslationStashStorage( $db);}, 'Translate:TranslationStatsDataProvider'=> static function(MediaWikiServices $services):TranslationStatsDataProvider { return new TranslationStatsDataProvider(new ServiceOptions(TranslationStatsDataProvider::CONSTRUCTOR_OPTIONS, $services->getMainConfig()), $services->getObjectFactory());}, 'Translate:TranslationUnitStoreFactory'=> static function(MediaWikiServices $services):TranslationUnitStoreFactory { return new TranslationUnitStoreFactory( $services->getDBLoadBalancer());}, 'Translate:TranslatorActivity'=> static function(MediaWikiServices $services):TranslatorActivity { $query=new TranslatorActivityQuery($services->getMainConfig(), $services->getDBLoadBalancer());return new TranslatorActivity($services->getMainObjectStash(), $query, $services->getJobQueueGroup());}, 'Translate:TtmServerFactory'=> static function(MediaWikiServices $services):TtmServerFactory { $config=$services->getMainConfig();$default=$config->get( 'TranslateTranslationDefaultService');if( $default===false) { $default=null;} return new TtmServerFactory( $config->get( 'TranslateTranslationServices'), $default);}]
@phpcs-require-sorted-array
Storage on CDB files.
retrieve(bool $readLatest=false)
Storage on the object cache.
Storage on the database itself.
Storage on hash.
clearMessageGroupStats(array $diff)
Purge stuff when set of keys have changed.
Factory class for accessing message groups individually by id or all of them as a list.
Minimal service container.
Definition Services.php:40
Essentially random collection of helper functions, similar to GlobalFunctions.php.
Definition Utilities.php:30
Class for pointing to messages, like Title class is for titles.
getTitle()
Get the original title.
getKey()
Returns the identified or guessed message key.
Creates a database of keys in all groups, so that namespace and key can be used to get the groups the...
clearMessageGroupStats(array $diff)
Purge stuff when set of keys have changed.
checkAndAdd(&$hugearray, MessageGroup $g, $ignore=false)
static getPrimaryGroupId(MessageHandle $handle)
serialize( $data)
These are probably slower than serialize and unserialize, but they are more space efficient because w...
rebuild(float $timestamp=null)
Creates the index from scratch.
static singleton()
static setInstance(self $instance)
Override the global instance, for testing.
static getArrayDiff(array $old, array $new)
Compares two associative arrays.
static getGroupIds(MessageHandle $handle)
Retrieves a list of groups given MessageHandle belongs to.
Storage on serialized file.
Interface for message groups.
getNamespace()
Returns the namespace where messages are placed.
getId()
Returns the unique identifier for this group.
getKeys()
Shortcut for array_keys( getDefinitions() ) that can be optimized by the implementing classes.