MediaWiki  master
SlotRecord.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Revision;
24 
25 use Content;
30 
39 class SlotRecord {
40 
41  const MAIN = 'main';
42 
48  private $row;
49 
53  private $content;
54 
63  public static function newWithSuppressedContent( SlotRecord $slot ) {
64  $row = $slot->row;
65 
66  return new SlotRecord( $row, function () {
67  throw new SuppressedDataException( 'Content suppressed!' );
68  } );
69  }
70 
80  private static function newDerived( SlotRecord $slot, array $overrides = [] ) {
81  $row = clone $slot->row;
82  $row->slot_id = null; // never copy the row ID!
83 
84  foreach ( $overrides as $key => $value ) {
85  $row->$key = $value;
86  }
87 
88  return new SlotRecord( $row, $slot->content );
89  }
90 
103  public static function newInherited( SlotRecord $slot ) {
104  // Sanity check - we can't inherit from a Slot that's not attached to a revision.
105  $slot->getRevision();
106  $slot->getOrigin();
107  $slot->getAddress();
108 
109  // NOTE: slot_origin and content_address are copied from $slot.
110  return self::newDerived( $slot, [
111  'slot_revision_id' => null,
112  ] );
113  }
114 
129  public static function newUnsaved( $role, Content $content ) {
130  Assert::parameterType( 'string', $role, '$role' );
131 
132  $row = [
133  'slot_id' => null, // not yet known
134  'slot_revision_id' => null, // not yet known
135  'slot_origin' => null, // not yet known, will be set in newSaved()
136  'content_size' => null, // compute later
137  'content_sha1' => null, // compute later
138  'slot_content_id' => null, // not yet known, will be set in newSaved()
139  'content_address' => null, // not yet known, will be set in newSaved()
140  'role_name' => $role,
141  'model_name' => $content->getModel(),
142  ];
143 
144  return new SlotRecord( (object)$row, $content );
145  }
146 
164  public static function newSaved(
165  $revisionId,
166  $contentId,
167  $contentAddress,
168  SlotRecord $protoSlot
169  ) {
170  Assert::parameterType( 'integer', $revisionId, '$revisionId' );
171  // TODO once migration is over $contentId must be an integer
172  Assert::parameterType( 'integer|null', $contentId, '$contentId' );
173  Assert::parameterType( 'string', $contentAddress, '$contentAddress' );
174 
175  if ( $protoSlot->hasRevision() && $protoSlot->getRevision() !== $revisionId ) {
176  throw new LogicException(
177  "Mismatching revision ID $revisionId: "
178  . "The slot already belongs to revision {$protoSlot->getRevision()}. "
179  . "Use SlotRecord::newInherited() to re-use content between revisions."
180  );
181  }
182 
183  if ( $protoSlot->hasAddress() && $protoSlot->getAddress() !== $contentAddress ) {
184  throw new LogicException(
185  "Mismatching blob address $contentAddress: "
186  . "The slot already has content at {$protoSlot->getAddress()}."
187  );
188  }
189 
190  if ( $protoSlot->hasContentId() && $protoSlot->getContentId() !== $contentId ) {
191  throw new LogicException(
192  "Mismatching content ID $contentId: "
193  . "The slot already has content row {$protoSlot->getContentId()} associated."
194  );
195  }
196 
197  if ( $protoSlot->isInherited() ) {
198  if ( !$protoSlot->hasAddress() ) {
199  throw new InvalidArgumentException(
200  "An inherited blob should have a content address!"
201  );
202  }
203  if ( !$protoSlot->hasField( 'slot_origin' ) ) {
204  throw new InvalidArgumentException(
205  "A saved inherited slot should have an origin set!"
206  );
207  }
208  $origin = $protoSlot->getOrigin();
209  } else {
210  $origin = $revisionId;
211  }
212 
213  return self::newDerived( $protoSlot, [
214  'slot_revision_id' => $revisionId,
215  'slot_content_id' => $contentId,
216  'slot_origin' => $origin,
217  'content_address' => $contentAddress,
218  ] );
219  }
220 
234  public function __construct( $row, $content ) {
235  Assert::parameterType( 'object', $row, '$row' );
236  Assert::parameterType( 'Content|callable', $content, '$content' );
237 
238  Assert::parameter(
239  property_exists( $row, 'slot_revision_id' ),
240  '$row->slot_revision_id',
241  'must exist'
242  );
243  Assert::parameter(
244  property_exists( $row, 'slot_content_id' ),
245  '$row->slot_content_id',
246  'must exist'
247  );
248  Assert::parameter(
249  property_exists( $row, 'content_address' ),
250  '$row->content_address',
251  'must exist'
252  );
253  Assert::parameter(
254  property_exists( $row, 'model_name' ),
255  '$row->model_name',
256  'must exist'
257  );
258  Assert::parameter(
259  property_exists( $row, 'slot_origin' ),
260  '$row->slot_origin',
261  'must exist'
262  );
263  Assert::parameter(
264  !property_exists( $row, 'slot_inherited' ),
265  '$row->slot_inherited',
266  'must not exist'
267  );
268  Assert::parameter(
269  !property_exists( $row, 'slot_revision' ),
270  '$row->slot_revision',
271  'must not exist'
272  );
273 
274  $this->row = $row;
275  $this->content = $content;
276  }
277 
283  public function __sleep() {
284  throw new LogicException( __CLASS__ . ' is not serializable.' );
285  }
286 
302  public function getContent() {
303  if ( $this->content instanceof Content ) {
304  return $this->content;
305  }
306 
307  $obj = call_user_func( $this->content, $this );
308 
309  Assert::postcondition(
310  $obj instanceof Content,
311  'Slot content callback should return a Content object'
312  );
313 
314  $this->content = $obj;
315 
316  return $this->content;
317  }
318 
329  private function getField( $name ) {
330  if ( !isset( $this->row->$name ) ) {
331  // distinguish between unknown and uninitialized fields
332  if ( property_exists( $this->row, $name ) ) {
333  throw new IncompleteRevisionException( 'Uninitialized field: ' . $name );
334  } else {
335  throw new OutOfBoundsException( 'No such field: ' . $name );
336  }
337  }
338 
339  $value = $this->row->$name;
340 
341  // NOTE: allow callbacks, but don't trust plain string callables from the database!
342  if ( !is_string( $value ) && is_callable( $value ) ) {
343  $value = call_user_func( $value, $this );
344  $this->setField( $name, $value );
345  }
346 
347  return $value;
348  }
349 
359  private function getStringField( $name ) {
360  return strval( $this->getField( $name ) );
361  }
362 
372  private function getIntField( $name ) {
373  return intval( $this->getField( $name ) );
374  }
375 
380  private function hasField( $name ) {
381  if ( isset( $this->row->$name ) ) {
382  // if the field is a callback, resolve first, then re-check
383  if ( !is_string( $this->row->$name ) && is_callable( $this->row->$name ) ) {
384  $this->getField( $name );
385  }
386  }
387 
388  return isset( $this->row->$name );
389  }
390 
396  public function getRevision() {
397  return $this->getIntField( 'slot_revision_id' );
398  }
399 
405  public function getOrigin() {
406  return $this->getIntField( 'slot_origin' );
407  }
408 
420  public function isInherited() {
421  if ( $this->hasRevision() ) {
422  return $this->getRevision() !== $this->getOrigin();
423  } else {
424  return $this->hasAddress();
425  }
426  }
427 
435  public function hasAddress() {
436  return $this->hasField( 'content_address' );
437  }
438 
446  public function hasOrigin() {
447  return $this->hasField( 'slot_origin' );
448  }
449 
469  public function hasContentId() {
470  return $this->hasField( 'slot_content_id' );
471  }
472 
480  public function hasRevision() {
481  return $this->hasField( 'slot_revision_id' );
482  }
483 
489  public function getRole() {
490  return $this->getStringField( 'role_name' );
491  }
492 
499  public function getAddress() {
500  return $this->getStringField( 'content_address' );
501  }
502 
513  public function getContentId() {
514  return $this->getIntField( 'slot_content_id' );
515  }
516 
522  public function getSize() {
523  try {
524  $size = $this->getIntField( 'content_size' );
525  } catch ( IncompleteRevisionException $ex ) {
526  $size = $this->getContent()->getSize();
527  $this->setField( 'content_size', $size );
528  }
529 
530  return $size;
531  }
532 
538  public function getSha1() {
539  try {
540  $sha1 = $this->getStringField( 'content_sha1' );
541  } catch ( IncompleteRevisionException $ex ) {
542  $format = $this->hasField( 'format_name' )
543  ? $this->getStringField( 'format_name' )
544  : null;
545 
546  $data = $this->getContent()->serialize( $format );
547  $sha1 = self::base36Sha1( $data );
548  $this->setField( 'content_sha1', $sha1 );
549  }
550 
551  return $sha1;
552  }
553 
561  public function getModel() {
562  try {
563  $model = $this->getStringField( 'model_name' );
564  } catch ( IncompleteRevisionException $ex ) {
565  $model = $this->getContent()->getModel();
566  $this->setField( 'model_name', $model );
567  }
568 
569  return $model;
570  }
571 
581  public function getFormat() {
582  // XXX: we currently do not plan to store the format for each slot!
583 
584  if ( $this->hasField( 'format_name' ) ) {
585  return $this->getStringField( 'format_name' );
586  }
587 
588  return null;
589  }
590 
595  private function setField( $name, $value ) {
596  $this->row->$name = $value;
597  }
598 
607  public static function base36Sha1( $blob ) {
608  return \Wikimedia\base_convert( sha1( $blob ), 16, 36, 31 );
609  }
610 
630  public function hasSameContent( SlotRecord $other ) {
631  if ( $other === $this ) {
632  return true;
633  }
634 
635  if ( $this->getModel() !== $other->getModel() ) {
636  return false;
637  }
638 
639  if ( $this->hasAddress()
640  && $other->hasAddress()
641  && $this->getAddress() == $other->getAddress()
642  ) {
643  return true;
644  }
645 
646  if ( $this->getSize() !== $other->getSize() ) {
647  return false;
648  }
649 
650  if ( $this->getSha1() !== $other->getSha1() ) {
651  return false;
652  }
653 
654  return true;
655  }
656 
657 }
658 
663 class_alias( SlotRecord::class, 'MediaWiki\Storage\SlotRecord' );
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
object $row
database result row, as a raw object.
Definition: SlotRecord.php:48
static newSaved( $revisionId, $contentId, $contentAddress, SlotRecord $protoSlot)
Constructs a complete SlotRecord for a newly saved revision, based on the incomplete proto-slot...
Definition: SlotRecord.php:164
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
per default it will return the text for text based content
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
__construct( $row, $content)
The following fields are supported by the $row parameter:
Definition: SlotRecord.php:234
$value
Exception throw when trying to access undefined fields on an incomplete RevisionRecord.
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
getContent()
Returns the Content of the given slot.
Definition: SlotRecord.php:302
getModel()
Returns the content model.
Definition: SlotRecord.php:561
isInherited()
Whether this slot was inherited from an older revision.
Definition: SlotRecord.php:420
Created by PhpStorm.
static newInherited(SlotRecord $slot)
Constructs a new SlotRecord for a new revision, inheriting the content of the given SlotRecord of a p...
Definition: SlotRecord.php:103
getFormat()
Returns the blob serialization format as a MIME type.
Definition: SlotRecord.php:581
static newWithSuppressedContent(SlotRecord $slot)
Returns a new SlotRecord just like the given $slot, except that calling getContent() will fail with a...
Definition: SlotRecord.php:63
hasOrigin()
Whether this slot has an origin (revision ID that originated the slot&#39;s content.
Definition: SlotRecord.php:446
static newDerived(SlotRecord $slot, array $overrides=[])
Constructs a new SlotRecord from an existing SlotRecord, overriding some fields.
Definition: SlotRecord.php:80
getField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:329
getRevision()
Returns the ID of the revision this slot is associated with.
Definition: SlotRecord.php:396
Exception raised in response to an audience check when attempting to access suppressed information wi...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:780
getStringField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:359
hasContentId()
Whether this slot has a content ID.
Definition: SlotRecord.php:469
__sleep()
Implemented to defy serialization.
Definition: SlotRecord.php:283
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
getOrigin()
Returns the revision ID of the revision that originated the slot&#39;s content.
Definition: SlotRecord.php:405
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:489
static base36Sha1( $blob)
Get the base 36 SHA-1 value for a string of text.
Definition: SlotRecord.php:607
getSha1()
Returns the content size.
Definition: SlotRecord.php:538
hasRevision()
Whether this slot has revision ID associated.
Definition: SlotRecord.php:480
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
getContentId()
Returns the ID of the content meta data row associated with the slot.
Definition: SlotRecord.php:513
getSize()
Returns the content size.
Definition: SlotRecord.php:522
Content callable $content
Definition: SlotRecord.php:53
getAddress()
Returns the address of this slot&#39;s content.
Definition: SlotRecord.php:499
static newUnsaved( $role, Content $content)
Constructs a new Slot from a Content object for a new revision.
Definition: SlotRecord.php:129
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
hasSameContent(SlotRecord $other)
Returns true if $other has the same content as this slot.
Definition: SlotRecord.php:630
hasAddress()
Whether this slot has an address.
Definition: SlotRecord.php:435
getIntField( $name)
Returns the int value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:372
getModel()
Returns the ID of the content model used by this Content object.
setField( $name, $value)
Definition: SlotRecord.php:595