MediaWiki REL1_37
ActorMigrationBase.php
Go to the documentation of this file.
1<?php
25use Wikimedia\IPUtils;
27
37 private $joinCache = [];
38
40 private $readStage;
41
43 private $writeStage;
44
47
49 private $fieldInfos;
50
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}
const SCHEMA_COMPAT_WRITE_TEMP
Definition Defines.php:264
const SCHEMA_COMPAT_READ_NEW
Definition Defines.php:267
const SCHEMA_COMPAT_WRITE_OLD
Definition Defines.php:262
const SCHEMA_COMPAT_READ_TEMP
Definition Defines.php:265
const SCHEMA_COMPAT_READ_OLD
Definition Defines.php:263
const SCHEMA_COMPAT_WRITE_NEW
Definition Defines.php:266
const SCHEMA_COMPAT_WRITE_MASK
Definition Defines.php:268
const SCHEMA_COMPAT_READ_MASK
Definition Defines.php:269
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
This abstract base class helps migrate core and extension code to use the actor table.
getJoin( $key)
Get SELECT fields and joins for the actor key.
getTempTableInfo( $key)
Convenience function for getting temp table config.
int $writeStage
A combination of the SCHEMA_COMPAT_WRITE_* flags.
getInstanceName()
Get a name for this instance to use in error messages.
checkDeprecation( $key)
Issue deprecation warning/error as appropriate.
ActorStoreFactory $actorStoreFactory
getInsertValues(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
array[] $joinCache
Cache for self::getJoin()
isNotAnon( $field)
Return an SQL condition to test if a user field is non-anonymous.
getWhere(IDatabase $db, $key, $users, $useId=true)
Get WHERE condition for the actor.
__construct( $fieldInfos, $stage, ActorStoreFactory $actorStoreFactory, $options=[])
getInsertValuesWithTempTable(IDatabase $dbw, $key, UserIdentity $user)
Get UPDATE fields for the actor.
getFieldInfo( $key)
Get config information about a field.
int $readStage
One of the SCHEMA_COMPAT_READ_* values.
isAnon( $field)
Return an SQL condition to test if a user field is anonymous.
Interface for objects representing user identity.
getId( $wikiId=self::LOCAL)
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:38
makeList(array $a, $mode=self::LIST_COMMA)
Makes an encoded list of strings from an array.
getDomainID()
Return the currently selected domain ID.
upsert( $table, array $rows, $uniqueKeys, array $set, $fname=__METHOD__)
Upsert the given row(s) into a table.