MediaWiki  master
ActorMigrationBase.php
Go to the documentation of this file.
1 <?php
25 use Wikimedia\IPUtils;
27 
37  private $joinCache = [];
38 
40  private $readStage;
41 
43  private $writeStage;
44 
47 
49  private $fieldInfos;
50 
52  private $allowUnknown;
53 
97  public function __construct(
99  $stage,
101  $options = []
102  ) {
103  $this->fieldInfos = $fieldInfos;
104  $this->allowUnknown = $options['allowUnknown'] ?? true;
105 
108  if ( $writeStage === 0 ) {
109  throw new InvalidArgumentException( '$stage must include a write mode' );
110  }
111  if ( $readStage === 0 ) {
112  throw new InvalidArgumentException( '$stage must include a read mode' );
113  }
114  if ( !in_array( $readStage,
116  ) {
117  throw new InvalidArgumentException( 'Cannot read multiple schemas' );
118  }
120  throw new InvalidArgumentException( 'Cannot read the old schema without also writing it' );
121  }
123  throw new InvalidArgumentException( 'Cannot read the temp schema without also writing it' );
124  }
126  throw new InvalidArgumentException( 'Cannot read the new schema without also writing it' );
127  }
128  $this->readStage = $readStage;
129  $this->writeStage = $writeStage;
130 
131  $this->actorStoreFactory = $actorStoreFactory;
132  }
133 
142  protected function getFieldInfo( $key ) {
143  if ( isset( $this->fieldInfos[$key] ) ) {
144  return $this->fieldInfos[$key];
145  } elseif ( $this->allowUnknown ) {
146  return [];
147  } else {
148  throw new InvalidArgumentException( $this->getInstanceName() . ": unknown key $key" );
149  }
150  }
151 
160  protected function getInstanceName() {
161  if ( ( new ReflectionClass( $this ) )->isAnonymous() ) {
162  // Mostly for PHPUnit
163  return self::class;
164  } else {
165  return static::class;
166  }
167  }
168 
176  protected function checkDeprecation( $key ) {
177  $fieldInfo = $this->getFieldInfo( $key );
178  if ( isset( $fieldInfo['removedVersion'] ) ) {
179  $removedVersion = $fieldInfo['removedVersion'];
180  $component = $fieldInfo['component'] ?? 'MediaWiki';
181  throw new InvalidArgumentException(
182  "Use of {$this->getInstanceName()} for '$key' was removed in $component $removedVersion"
183  );
184  }
185  if ( isset( $fieldInfo['deprecatedVersion'] ) ) {
186  $deprecatedVersion = $fieldInfo['deprecatedVersion'];
187  $component = $fieldInfo['component'] ?? 'MediaWiki';
188  wfDeprecated( "{$this->getInstanceName()} for '$key'", $deprecatedVersion, $component, 3 );
189  }
190  }
191 
197  public function isAnon( $field ) {
198  return ( $this->readStage >= SCHEMA_COMPAT_READ_TEMP ) ? "$field IS NULL" : "$field = 0";
199  }
200 
206  public function isNotAnon( $field ) {
207  return ( $this->readStage >= SCHEMA_COMPAT_READ_TEMP ) ? "$field IS NOT NULL" : "$field != 0";
208  }
209 
215  private function getFieldNames( $key ) {
216  $fieldInfo = $this->getFieldInfo( $key );
217  $textField = $fieldInfo['textField'] ?? $key . '_text';
218  $actorField = $fieldInfo['actorField'] ?? substr( $key, 0, -5 ) . '_actor';
219  return [ $textField, $actorField ];
220  }
221 
228  private function getTempTableInfo( $key ) {
229  $fieldInfo = $this->getFieldInfo( $key );
230  return $fieldInfo['tempTable'] ?? null;
231  }
232 
245  public function getJoin( $key ) {
246  $this->checkDeprecation( $key );
247 
248  if ( !isset( $this->joinCache[$key] ) ) {
249  $tables = [];
250  $fields = [];
251  $joins = [];
252 
253  list( $text, $actor ) = $this->getFieldNames( $key );
254 
255  if ( $this->readStage === SCHEMA_COMPAT_READ_OLD ) {
256  $fields[$key] = $key;
257  $fields[$text] = $text;
258  $fields[$actor] = 'NULL';
259  } elseif ( $this->readStage === SCHEMA_COMPAT_READ_TEMP ) {
260  $tempTableInfo = $this->getTempTableInfo( $key );
261  if ( $tempTableInfo ) {
262  $alias = "temp_$key";
263  $tables[$alias] = $tempTableInfo['table'];
264  $joins[$alias] = [ 'JOIN',
265  "{$alias}.{$tempTableInfo['pk']} = {$tempTableInfo['joinPK']}" ];
266  $joinField = "{$alias}.{$tempTableInfo['field']}";
267  } else {
268  $joinField = $actor;
269  }
270 
271  $alias = "actor_$key";
272  $tables[$alias] = 'actor';
273  $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$joinField}" ];
274 
275  $fields[$key] = "{$alias}.actor_user";
276  $fields[$text] = "{$alias}.actor_name";
277  $fields[$actor] = $joinField;
278  } else /* SCHEMA_COMPAT_READ_NEW */ {
279  $alias = "actor_$key";
280  $tables[$alias] = 'actor';
281  $joins[$alias] = [ 'JOIN', "{$alias}.actor_id = {$actor}" ];
282 
283  $fields[$key] = "{$alias}.actor_user";
284  $fields[$text] = "{$alias}.actor_name";
285  $fields[$actor] = $actor;
286  }
287 
288  $this->joinCache[$key] = [
289  'tables' => $tables,
290  'fields' => $fields,
291  'joins' => $joins,
292  ];
293  }
294 
295  return $this->joinCache[$key];
296  }
297 
307  public function getInsertValues( IDatabase $dbw, $key, UserIdentity $user ) {
308  $this->checkDeprecation( $key );
309 
310  if ( $this->getTempTableInfo( $key ) ) {
311  throw new InvalidArgumentException( "Must use getInsertValuesWithTempTable() for $key" );
312  }
313 
314  list( $text, $actor ) = $this->getFieldNames( $key );
315  $ret = [];
316  if ( $this->writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
317  $ret[$key] = $user->getId();
318  $ret[$text] = $user->getName();
319  }
320  if ( $this->writeStage & SCHEMA_COMPAT_WRITE_TEMP
321  || $this->writeStage & SCHEMA_COMPAT_WRITE_NEW
322  ) {
323  $ret[$actor] = $this->actorStoreFactory
324  ->getActorNormalization( $dbw->getDomainID() )
325  ->acquireActorId( $user, $dbw );
326  }
327  return $ret;
328  }
329 
342  public function getInsertValuesWithTempTable( IDatabase $dbw, $key, UserIdentity $user ) {
343  $this->checkDeprecation( $key );
344 
345  $fieldInfo = $this->getFieldInfo( $key );
346  $tempTableInfo = $fieldInfo['tempTable'] ?? null;
347  if ( isset( $fieldInfo['formerTempTableVersion'] ) ) {
348  wfDeprecated( __METHOD__ . " for $key",
349  $fieldInfo['formerTempTableVersion'],
350  $fieldInfo['component'] ?? 'MediaWiki' );
351  } elseif ( !$tempTableInfo ) {
352  throw new InvalidArgumentException( "Must use getInsertValues() for $key" );
353  }
354 
355  list( $text, $actor ) = $this->getFieldNames( $key );
356  $ret = [];
357  $callback = null;
358 
359  if ( $this->writeStage & SCHEMA_COMPAT_WRITE_OLD ) {
360  $ret[$key] = $user->getId();
361  $ret[$text] = $user->getName();
362  }
363  if ( $this->writeStage & ( SCHEMA_COMPAT_WRITE_TEMP | SCHEMA_COMPAT_WRITE_NEW ) ) {
364  $id = $this->actorStoreFactory
365  ->getActorNormalization( $dbw->getDomainID() )
366  ->acquireActorId( $user, $dbw );
367 
368  if ( $tempTableInfo ) {
369  if ( $this->writeStage & SCHEMA_COMPAT_WRITE_TEMP ) {
370  $func = __METHOD__;
371  $callback = static function ( $pk, array $extra ) use ( $tempTableInfo, $dbw, $id, $func ) {
372  $set = [ $tempTableInfo['field'] => $id ];
373  foreach ( $tempTableInfo['extra'] as $to => $from ) {
374  if ( !array_key_exists( $from, $extra ) ) {
375  throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
376  }
377  $set[$to] = $extra[$from];
378  }
379  $dbw->upsert(
380  $tempTableInfo['table'],
381  [ $tempTableInfo['pk'] => $pk ] + $set,
382  [ [ $tempTableInfo['pk'] ] ],
383  $set,
384  $func
385  );
386  };
387  }
388  if ( $this->writeStage & SCHEMA_COMPAT_WRITE_NEW ) {
389  $ret[$actor] = $id;
390  }
391  } else {
392  $ret[$actor] = $id;
393  }
394  }
395 
396  if ( $callback === null ) {
397  // Make a validation-only callback if there was temp table info
398  if ( $tempTableInfo ) {
399  $func = __METHOD__;
400  $callback = static function ( $pk, array $extra ) use ( $tempTableInfo, $func ) {
401  foreach ( $tempTableInfo['extra'] as $to => $from ) {
402  if ( !array_key_exists( $from, $extra ) ) {
403  throw new InvalidArgumentException( "$func callback: \$extra[$from] is not provided" );
404  }
405  }
406  };
407  } else {
408  $callback = static function ( $pk, array $extra ) {
409  };
410  }
411  }
412  return [ $ret, $callback ];
413  }
414 
438  public function getWhere( IDatabase $db, $key, $users, $useId = true ) {
439  $this->checkDeprecation( $key );
440 
441  $tables = [];
442  $conds = [];
443  $joins = [];
444 
445  if ( $users instanceof UserIdentity ) {
446  $users = [ $users ];
447  } elseif ( $users === null || $users === false ) {
448  // DWIM
449  $users = [];
450  } elseif ( !is_array( $users ) ) {
451  $what = is_object( $users ) ? get_class( $users ) : gettype( $users );
452  throw new InvalidArgumentException(
453  __METHOD__ . ": Value for \$users must be a UserIdentity or array, got $what"
454  );
455  }
456 
457  // Get information about all the passed users
458  $ids = [];
459  $names = [];
460  $actors = [];
461  foreach ( $users as $user ) {
462  if ( $useId && $user->getId() ) {
463  $ids[] = $user->getId();
464  } else {
465  // make sure to use normalized form of IP for anonymous users
466  $names[] = IPUtils::sanitizeIP( $user->getName() );
467  }
468  $actorId = $this->actorStoreFactory
469  ->getActorNormalization( $db->getDomainID() )
470  ->findActorId( $user, $db );
471 
472  if ( $actorId ) {
473  $actors[] = $actorId;
474  }
475  }
476 
477  list( $text, $actor ) = $this->getFieldNames( $key );
478 
479  // Combine data into conditions to be ORed together
480  if ( $this->readStage === SCHEMA_COMPAT_READ_NEW ) {
481  if ( $actors ) {
482  $conds['newactor'] = $db->makeList( [ $actor => $actors ], IDatabase::LIST_AND );
483  }
484  } elseif ( $this->readStage === SCHEMA_COMPAT_READ_TEMP ) {
485  if ( $actors ) {
486  $tempTableInfo = $this->getTempTableInfo( $key );
487  if ( $tempTableInfo ) {
488  $alias = "temp_$key";
489  $tables[$alias] = $tempTableInfo['table'];
490  $joins[$alias] = [ 'JOIN',
491  "{$alias}.{$tempTableInfo['pk']} = {$tempTableInfo['joinPK']}" ];
492  $joinField = "{$alias}.{$tempTableInfo['field']}";
493  } else {
494  $joinField = $actor;
495  }
496  $conds['actor'] = $db->makeList( [ $joinField => $actors ], IDatabase::LIST_AND );
497  }
498  } else {
499  if ( $ids ) {
500  $conds['userid'] = $db->makeList( [ $key => $ids ], IDatabase::LIST_AND );
501  }
502  if ( $names ) {
503  $conds['username'] = $db->makeList( [ $text => $names ], IDatabase::LIST_AND );
504  }
505  }
506 
507  return [
508  'tables' => $tables,
509  'conds' => $conds ? $db->makeList( array_values( $conds ), IDatabase::LIST_OR ) : '1=0',
510  'orconds' => $conds,
511  'joins' => $joins,
512  ];
513  }
514 }
LIST_OR
const LIST_OR
Definition: Defines.php:46
ActorMigrationBase\checkDeprecation
checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
Definition: ActorMigrationBase.php:176
ActorMigrationBase\$fieldInfos
array $fieldInfos
Definition: ActorMigrationBase.php:49
LIST_AND
const LIST_AND
Definition: Defines.php:43
Wikimedia\Rdbms\IDatabase\upsert
upsert( $table, array $rows, $uniqueKeys, array $set, $fname=__METHOD__)
Upsert the given row(s) into a table.
ActorMigrationBase\$joinCache
array[] $joinCache
Cache for self::getJoin()
Definition: ActorMigrationBase.php:37
MediaWiki\User\UserIdentity\getId
getId( $wikiId=self::LOCAL)
MediaWiki\User\ActorStoreFactory
Definition: ActorStoreFactory.php:35
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
ActorMigrationBase\$readStage
int $readStage
One of the SCHEMA_COMPAT_READ_* values.
Definition: ActorMigrationBase.php:40
ActorMigrationBase\getFieldInfo
getFieldInfo( $key)
Get config information about a field.
Definition: ActorMigrationBase.php:142
SCHEMA_COMPAT_READ_OLD
const SCHEMA_COMPAT_READ_OLD
Definition: Defines.php:263
ActorMigrationBase\getWhere
getWhere(IDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
Definition: ActorMigrationBase.php:438
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
ActorMigrationBase\getTempTableInfo
getTempTableInfo( $key)
Convenience function for getting temp table config.
Definition: ActorMigrationBase.php:228
SCHEMA_COMPAT_WRITE_OLD
const SCHEMA_COMPAT_WRITE_OLD
Definition: Defines.php:262
MediaWiki\User\UserIdentity\getName
getName()
ActorMigrationBase\$actorStoreFactory
ActorStoreFactory $actorStoreFactory
Definition: ActorMigrationBase.php:46
SCHEMA_COMPAT_WRITE_NEW
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:266
SCHEMA_COMPAT_WRITE_MASK
const SCHEMA_COMPAT_WRITE_MASK
Definition: Defines.php:268
ActorMigrationBase
This abstract base class helps migrate core and extension code to use the actor table.
Definition: ActorMigrationBase.php:35
Wikimedia\Rdbms\IDatabase\getDomainID
getDomainID()
Return the currently selected domain ID.
ActorMigrationBase\getInsertValues
getInsertValues(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
Definition: ActorMigrationBase.php:307
ActorMigrationBase\getJoin
getJoin( $key)
Get SELECT fields and joins for the actor key.
Definition: ActorMigrationBase.php:245
SCHEMA_COMPAT_WRITE_TEMP
const SCHEMA_COMPAT_WRITE_TEMP
Definition: Defines.php:264
ActorMigrationBase\getInsertValuesWithTempTable
getInsertValuesWithTempTable(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
Definition: ActorMigrationBase.php:342
ActorMigrationBase\__construct
__construct( $fieldInfos, $stage, ActorStoreFactory $actorStoreFactory, $options=[])
Definition: ActorMigrationBase.php:97
ActorMigrationBase\getFieldNames
getFieldNames( $key)
Definition: ActorMigrationBase.php:215
ActorMigrationBase\$allowUnknown
bool $allowUnknown
Definition: ActorMigrationBase.php:52
SCHEMA_COMPAT_READ_NEW
const SCHEMA_COMPAT_READ_NEW
Definition: Defines.php:267
SCHEMA_COMPAT_READ_MASK
const SCHEMA_COMPAT_READ_MASK
Definition: Defines.php:269
ActorMigrationBase\isAnon
isAnon( $field)
Return an SQL condition to test if a user field is anonymous.
Definition: ActorMigrationBase.php:197
Wikimedia\Rdbms\IDatabase\makeList
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
SCHEMA_COMPAT_READ_TEMP
const SCHEMA_COMPAT_READ_TEMP
Definition: Defines.php:265
ActorMigrationBase\isNotAnon
isNotAnon( $field)
Return an SQL condition to test if a user field is non-anonymous.
Definition: ActorMigrationBase.php:206
ActorMigrationBase\$writeStage
int $writeStage
A combination of the SCHEMA_COMPAT_WRITE_* flags.
Definition: ActorMigrationBase.php:43
ActorMigrationBase\getInstanceName
getInstanceName()
Get a name for this instance to use in error messages.
Definition: ActorMigrationBase.php:160