MediaWiki  1.32.0
SlotRecord.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Revision;
24 
25 use Content;
26 use InvalidArgumentException;
27 use LogicException;
28 use OutOfBoundsException;
29 use Wikimedia\Assert\Assert;
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 
236  public function __construct( $row, $content ) {
237  Assert::parameterType( 'object', $row, '$row' );
238  Assert::parameterType( 'Content|callable', $content, '$content' );
239 
240  Assert::parameter(
241  property_exists( $row, 'slot_revision_id' ),
242  '$row->slot_revision_id',
243  'must exist'
244  );
245  Assert::parameter(
246  property_exists( $row, 'slot_content_id' ),
247  '$row->slot_content_id',
248  'must exist'
249  );
250  Assert::parameter(
251  property_exists( $row, 'content_address' ),
252  '$row->content_address',
253  'must exist'
254  );
255  Assert::parameter(
256  property_exists( $row, 'model_name' ),
257  '$row->model_name',
258  'must exist'
259  );
260  Assert::parameter(
261  property_exists( $row, 'slot_origin' ),
262  '$row->slot_origin',
263  'must exist'
264  );
265  Assert::parameter(
266  !property_exists( $row, 'slot_inherited' ),
267  '$row->slot_inherited',
268  'must not exist'
269  );
270  Assert::parameter(
271  !property_exists( $row, 'slot_revision' ),
272  '$row->slot_revision',
273  'must not exist'
274  );
275 
276  $this->row = $row;
277  $this->content = $content;
278  }
279 
285  public function __sleep() {
286  throw new LogicException( __CLASS__ . ' is not serializable.' );
287  }
288 
304  public function getContent() {
305  if ( $this->content instanceof Content ) {
306  return $this->content;
307  }
308 
309  $obj = call_user_func( $this->content, $this );
310 
311  Assert::postcondition(
312  $obj instanceof Content,
313  'Slot content callback should return a Content object'
314  );
315 
316  $this->content = $obj;
317 
318  return $this->content;
319  }
320 
331  private function getField( $name ) {
332  if ( !isset( $this->row->$name ) ) {
333  // distinguish between unknown and uninitialized fields
334  if ( property_exists( $this->row, $name ) ) {
335  throw new IncompleteRevisionException( 'Uninitialized field: ' . $name );
336  } else {
337  throw new OutOfBoundsException( 'No such field: ' . $name );
338  }
339  }
340 
341  $value = $this->row->$name;
342 
343  // NOTE: allow callbacks, but don't trust plain string callables from the database!
344  if ( !is_string( $value ) && is_callable( $value ) ) {
345  $value = call_user_func( $value, $this );
346  $this->setField( $name, $value );
347  }
348 
349  return $value;
350  }
351 
361  private function getStringField( $name ) {
362  return strval( $this->getField( $name ) );
363  }
364 
374  private function getIntField( $name ) {
375  return intval( $this->getField( $name ) );
376  }
377 
382  private function hasField( $name ) {
383  if ( isset( $this->row->$name ) ) {
384  // if the field is a callback, resolve first, then re-check
385  if ( !is_string( $this->row->$name ) && is_callable( $this->row->$name ) ) {
386  $this->getField( $name );
387  }
388  }
389 
390  return isset( $this->row->$name );
391  }
392 
398  public function getRevision() {
399  return $this->getIntField( 'slot_revision_id' );
400  }
401 
407  public function getOrigin() {
408  return $this->getIntField( 'slot_origin' );
409  }
410 
422  public function isInherited() {
423  if ( $this->hasRevision() ) {
424  return $this->getRevision() !== $this->getOrigin();
425  } else {
426  return $this->hasAddress();
427  }
428  }
429 
437  public function hasAddress() {
438  return $this->hasField( 'content_address' );
439  }
440 
448  public function hasOrigin() {
449  return $this->hasField( 'slot_origin' );
450  }
451 
471  public function hasContentId() {
472  return $this->hasField( 'slot_content_id' );
473  }
474 
482  public function hasRevision() {
483  return $this->hasField( 'slot_revision_id' );
484  }
485 
491  public function getRole() {
492  return $this->getStringField( 'role_name' );
493  }
494 
501  public function getAddress() {
502  return $this->getStringField( 'content_address' );
503  }
504 
515  public function getContentId() {
516  return $this->getIntField( 'slot_content_id' );
517  }
518 
524  public function getSize() {
525  try {
526  $size = $this->getIntField( 'content_size' );
527  } catch ( IncompleteRevisionException $ex ) {
528  $size = $this->getContent()->getSize();
529  $this->setField( 'content_size', $size );
530  }
531 
532  return $size;
533  }
534 
540  public function getSha1() {
541  try {
542  $sha1 = $this->getStringField( 'content_sha1' );
543  } catch ( IncompleteRevisionException $ex ) {
544  $format = $this->hasField( 'format_name' )
545  ? $this->getStringField( 'format_name' )
546  : null;
547 
548  $data = $this->getContent()->serialize( $format );
549  $sha1 = self::base36Sha1( $data );
550  $this->setField( 'content_sha1', $sha1 );
551  }
552 
553  return $sha1;
554  }
555 
563  public function getModel() {
564  try {
565  $model = $this->getStringField( 'model_name' );
566  } catch ( IncompleteRevisionException $ex ) {
567  $model = $this->getContent()->getModel();
568  $this->setField( 'model_name', $model );
569  }
570 
571  return $model;
572  }
573 
583  public function getFormat() {
584  // XXX: we currently do not plan to store the format for each slot!
585 
586  if ( $this->hasField( 'format_name' ) ) {
587  return $this->getStringField( 'format_name' );
588  }
589 
590  return null;
591  }
592 
597  private function setField( $name, $value ) {
598  $this->row->$name = $value;
599  }
600 
609  public static function base36Sha1( $blob ) {
610  return \Wikimedia\base_convert( sha1( $blob ), 16, 36, 31 );
611  }
612 
632  public function hasSameContent( SlotRecord $other ) {
633  if ( $other === $this ) {
634  return true;
635  }
636 
637  if ( $this->getModel() !== $other->getModel() ) {
638  return false;
639  }
640 
641  if ( $this->hasAddress()
642  && $other->hasAddress()
643  && $this->getAddress() == $other->getAddress()
644  ) {
645  return true;
646  }
647 
648  if ( $this->getSize() !== $other->getSize() ) {
649  return false;
650  }
651 
652  if ( $this->getSha1() !== $other->getSha1() ) {
653  return false;
654  }
655 
656  return true;
657  }
658 
659 }
660 
665 class_alias( SlotRecord::class, 'MediaWiki\Storage\SlotRecord' );
Revision\SlotRecord\hasField
hasField( $name)
Definition: SlotRecord.php:382
Revision\IncompleteRevisionException
Exception throw when trying to access undefined fields on an incomplete RevisionRecord.
Definition: IncompleteRevisionException.php:31
Revision\SlotRecord\getContent
getContent()
Returns the Content of the given slot.
Definition: SlotRecord.php:304
content
per default it will return the text for text based content
Definition: contenthandler.txt:104
Revision\SlotRecord\hasAddress
hasAddress()
Whether this slot has an address.
Definition: SlotRecord.php:437
Revision\SlotRecord\newInherited
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
Revision\SuppressedDataException
Exception raised in response to an audience check when attempting to access suppressed information wi...
Definition: SuppressedDataException.php:32
Revision\SlotRecord\getFormat
getFormat()
Returns the blob serialization format as a MIME type.
Definition: SlotRecord.php:583
Revision\SlotRecord\getIntField
getIntField( $name)
Returns the int value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:374
Revision\SlotRecord\hasOrigin
hasOrigin()
Whether this slot has an origin (revision ID that originated the slot's content.
Definition: SlotRecord.php:448
Revision\SlotRecord\isInherited
isInherited()
Whether this slot was inherited from an older revision.
Definition: SlotRecord.php:422
Revision\SlotRecord\getRevision
getRevision()
Returns the ID of the revision this slot is associated with.
Definition: SlotRecord.php:398
Revision\SlotRecord\newDerived
static newDerived(SlotRecord $slot, array $overrides=[])
Constructs a new SlotRecord from an existing SlotRecord, overriding some fields.
Definition: SlotRecord.php:80
php
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
MediaWiki\Revision
Created by PhpStorm.
Definition: IncompleteRevisionException.php:23
Revision\SlotRecord\getOrigin
getOrigin()
Returns the revision ID of the revision that originated the slot's content.
Definition: SlotRecord.php:407
$blob
$blob
Definition: testCompression.php:65
Revision\SlotRecord\__construct
__construct( $row, $content)
SlotRecord constructor.
Definition: SlotRecord.php:236
Revision\SlotRecord\getRole
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:491
Revision\SlotRecord\hasContentId
hasContentId()
Whether this slot has a content ID.
Definition: SlotRecord.php:471
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
Revision\SlotRecord\getModel
getModel()
Returns the content model.
Definition: SlotRecord.php:563
Revision\SlotRecord\getAddress
getAddress()
Returns the address of this slot's content.
Definition: SlotRecord.php:501
Revision\SlotRecord\base36Sha1
static base36Sha1( $blob)
Get the base 36 SHA-1 value for a string of text.
Definition: SlotRecord.php:609
array
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))
Revision\SlotRecord\newWithSuppressedContent
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
Revision\SlotRecord\getSha1
getSha1()
Returns the content size.
Definition: SlotRecord.php:540
Revision\SlotRecord\newUnsaved
static newUnsaved( $role, Content $content)
Constructs a new Slot from a Content object for a new revision.
Definition: SlotRecord.php:129
Revision\SlotRecord\hasRevision
hasRevision()
Whether this slot has revision ID associated.
Definition: SlotRecord.php:482
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
Revision\SlotRecord\getSize
getSize()
Returns the content size.
Definition: SlotRecord.php:524
$value
$value
Definition: styleTest.css.php:49
Revision\SlotRecord\getField
getField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:331
Revision\SlotRecord\MAIN
const MAIN
Definition: SlotRecord.php:41
Revision\SlotRecord\getStringField
getStringField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:361
Content
Base interface for content objects.
Definition: Content.php:34
Revision\SlotRecord\setField
setField( $name, $value)
Definition: SlotRecord.php:597
Revision\SlotRecord\__sleep
__sleep()
Implemented to defy serialization.
Definition: SlotRecord.php:285
Revision\SlotRecord\$row
object $row
database result row, as a raw object.
Definition: SlotRecord.php:48
Revision\SlotRecord\newSaved
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
as
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
Content\getModel
getModel()
Returns the ID of the content model used by this Content object.
class
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
Revision\SlotRecord\getContentId
getContentId()
Returns the ID of the content meta data row associated with the slot.
Definition: SlotRecord.php:515
Revision\SlotRecord\$content
Content callable $content
Definition: SlotRecord.php:53
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
Revision\SlotRecord\hasSameContent
hasSameContent(SlotRecord $other)
Returns true if $other has the same content as this slot.
Definition: SlotRecord.php:632