MediaWiki  REL1_31
SlotRecord.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Storage;
24 
25 use Content;
26 use InvalidArgumentException;
27 use LogicException;
28 use OutOfBoundsException;
29 use Wikimedia\Assert\Assert;
30 
38 class SlotRecord {
39 
43  private $row;
44 
48  private $content;
49 
58  public static function newWithSuppressedContent( SlotRecord $slot ) {
59  $row = $slot->row;
60 
61  return new SlotRecord( $row, function () {
62  throw new SuppressedDataException( 'Content suppressed!' );
63  } );
64  }
65 
75  private static function newDerived( SlotRecord $slot, array $overrides = [] ) {
76  $row = clone $slot->row;
77  $row->slot_id = null; // never copy the row ID!
78 
79  foreach ( $overrides as $key => $value ) {
80  $row->$key = $value;
81  }
82 
83  return new SlotRecord( $row, $slot->content );
84  }
85 
98  public static function newInherited( SlotRecord $slot ) {
99  // Sanity check - we can't inherit from a Slot that's not attached to a revision.
100  $slot->getRevision();
101  $slot->getOrigin();
102  $slot->getAddress();
103 
104  // NOTE: slot_origin and content_address are copied from $slot.
105  return self::newDerived( $slot, [
106  'slot_revision_id' => null,
107  ] );
108  }
109 
124  public static function newUnsaved( $role, Content $content ) {
125  Assert::parameterType( 'string', $role, '$role' );
126 
127  $row = [
128  'slot_id' => null, // not yet known
129  'slot_revision_id' => null, // not yet known
130  'slot_origin' => null, // not yet known, will be set in newSaved()
131  'content_size' => null, // compute later
132  'content_sha1' => null, // compute later
133  'slot_content_id' => null, // not yet known, will be set in newSaved()
134  'content_address' => null, // not yet known, will be set in newSaved()
135  'role_name' => $role,
136  'model_name' => $content->getModel(),
137  ];
138 
139  return new SlotRecord( (object)$row, $content );
140  }
141 
159  public static function newSaved(
160  $revisionId,
161  $contentId,
162  $contentAddress,
163  SlotRecord $protoSlot
164  ) {
165  Assert::parameterType( 'integer', $revisionId, '$revisionId' );
166  Assert::parameterType( 'integer', $contentId, '$contentId' );
167  Assert::parameterType( 'string', $contentAddress, '$contentAddress' );
168 
169  if ( $protoSlot->hasRevision() && $protoSlot->getRevision() !== $revisionId ) {
170  throw new LogicException(
171  "Mismatching revision ID $revisionId: "
172  . "The slot already belongs to revision {$protoSlot->getRevision()}. "
173  . "Use SlotRecord::newInherited() to re-use content between revisions."
174  );
175  }
176 
177  if ( $protoSlot->hasAddress() && $protoSlot->getAddress() !== $contentAddress ) {
178  throw new LogicException(
179  "Mismatching blob address $contentAddress: "
180  . "The slot already has content at {$protoSlot->getAddress()}."
181  );
182  }
183 
184  if ( $protoSlot->hasAddress() && $protoSlot->getContentId() !== $contentId ) {
185  throw new LogicException(
186  "Mismatching content ID $contentId: "
187  . "The slot already has content row {$protoSlot->getContentId()} associated."
188  );
189  }
190 
191  if ( $protoSlot->isInherited() ) {
192  if ( !$protoSlot->hasAddress() ) {
193  throw new InvalidArgumentException(
194  "An inherited blob should have a content address!"
195  );
196  }
197  if ( !$protoSlot->hasField( 'slot_origin' ) ) {
198  throw new InvalidArgumentException(
199  "A saved inherited slot should have an origin set!"
200  );
201  }
202  $origin = $protoSlot->getOrigin();
203  } else {
204  $origin = $revisionId;
205  }
206 
207  return self::newDerived( $protoSlot, [
208  'slot_revision_id' => $revisionId,
209  'slot_content_id' => $contentId,
210  'slot_origin' => $origin,
211  'content_address' => $contentAddress,
212  ] );
213  }
214 
230  public function __construct( $row, $content ) {
231  Assert::parameterType( 'object', $row, '$row' );
232  Assert::parameterType( 'Content|callable', $content, '$content' );
233 
234  Assert::parameter(
235  property_exists( $row, 'slot_id' ),
236  '$row->slot_id',
237  'must exist'
238  );
239  Assert::parameter(
240  property_exists( $row, 'slot_revision_id' ),
241  '$row->slot_revision_id',
242  'must exist'
243  );
244  Assert::parameter(
245  property_exists( $row, 'slot_content_id' ),
246  '$row->slot_content_id',
247  'must exist'
248  );
249  Assert::parameter(
250  property_exists( $row, 'content_address' ),
251  '$row->content_address',
252  'must exist'
253  );
254  Assert::parameter(
255  property_exists( $row, 'model_name' ),
256  '$row->model_name',
257  'must exist'
258  );
259  Assert::parameter(
260  property_exists( $row, 'slot_origin' ),
261  '$row->slot_origin',
262  'must exist'
263  );
264  Assert::parameter(
265  !property_exists( $row, 'slot_inherited' ),
266  '$row->slot_inherited',
267  'must not exist'
268  );
269  Assert::parameter(
270  !property_exists( $row, 'slot_revision' ),
271  '$row->slot_revision',
272  'must not exist'
273  );
274 
275  $this->row = $row;
276  $this->content = $content;
277  }
278 
284  public function __sleep() {
285  throw new LogicException( __CLASS__ . ' is not serializable.' );
286  }
287 
303  public function getContent() {
304  if ( $this->content instanceof Content ) {
305  return $this->content;
306  }
307 
308  $obj = call_user_func( $this->content, $this );
309 
310  Assert::postcondition(
311  $obj instanceof Content,
312  'Slot content callback should return a Content object'
313  );
314 
315  $this->content = $obj;
316 
317  return $this->content;
318  }
319 
330  private function getField( $name ) {
331  if ( !isset( $this->row->$name ) ) {
332  // distinguish between unknown and uninitialized fields
333  if ( property_exists( $this->row, $name ) ) {
334  throw new IncompleteRevisionException( 'Uninitialized field: ' . $name );
335  } else {
336  throw new OutOfBoundsException( 'No such field: ' . $name );
337  }
338  }
339 
340  $value = $this->row->$name;
341 
342  // NOTE: allow callbacks, but don't trust plain string callables from the database!
343  if ( !is_string( $value ) && is_callable( $value ) ) {
344  $value = call_user_func( $value, $this );
345  $this->setField( $name, $value );
346  }
347 
348  return $value;
349  }
350 
360  private function getStringField( $name ) {
361  return strval( $this->getField( $name ) );
362  }
363 
373  private function getIntField( $name ) {
374  return intval( $this->getField( $name ) );
375  }
376 
381  private function hasField( $name ) {
382  return isset( $this->row->$name );
383  }
384 
390  public function getRevision() {
391  return $this->getIntField( 'slot_revision_id' );
392  }
393 
399  public function getOrigin() {
400  return $this->getIntField( 'slot_origin' );
401  }
402 
414  public function isInherited() {
415  if ( $this->hasRevision() ) {
416  return $this->getRevision() !== $this->getOrigin();
417  } else {
418  return $this->hasAddress();
419  }
420  }
421 
429  public function hasAddress() {
430  return $this->hasField( 'content_address' );
431  }
432 
440  public function hasRevision() {
441  return $this->hasField( 'slot_revision_id' );
442  }
443 
449  public function getRole() {
450  return $this->getStringField( 'role_name' );
451  }
452 
459  public function getAddress() {
460  return $this->getStringField( 'content_address' );
461  }
462 
470  public function getContentId() {
471  return $this->getIntField( 'slot_content_id' );
472  }
473 
479  public function getSize() {
480  try {
481  $size = $this->getIntField( 'content_size' );
482  } catch ( IncompleteRevisionException $ex ) {
483  $size = $this->getContent()->getSize();
484  $this->setField( 'content_size', $size );
485  }
486 
487  return $size;
488  }
489 
495  public function getSha1() {
496  try {
497  $sha1 = $this->getStringField( 'content_sha1' );
498  } catch ( IncompleteRevisionException $ex ) {
499  $format = $this->hasField( 'format_name' )
500  ? $this->getStringField( 'format_name' )
501  : null;
502 
503  $data = $this->getContent()->serialize( $format );
504  $sha1 = self::base36Sha1( $data );
505  $this->setField( 'content_sha1', $sha1 );
506  }
507 
508  return $sha1;
509  }
510 
518  public function getModel() {
519  try {
520  $model = $this->getStringField( 'model_name' );
521  } catch ( IncompleteRevisionException $ex ) {
522  $model = $this->getContent()->getModel();
523  $this->setField( 'model_name', $model );
524  }
525 
526  return $model;
527  }
528 
538  public function getFormat() {
539  // XXX: we currently do not plan to store the format for each slot!
540 
541  if ( $this->hasField( 'format_name' ) ) {
542  return $this->getStringField( 'format_name' );
543  }
544 
545  return null;
546  }
547 
552  private function setField( $name, $value ) {
553  $this->row->$name = $value;
554  }
555 
564  public static function base36Sha1( $blob ) {
565  return \Wikimedia\base_convert( sha1( $blob ), 16, 36, 31 );
566  }
567 
568 }
MediaWiki\Storage\SlotRecord\getContentId
getContentId()
Returns the ID of the content meta data row associated with the slot.
Definition: SlotRecord.php:470
MediaWiki\Storage\SlotRecord\getSha1
getSha1()
Returns the content size.
Definition: SlotRecord.php:495
use
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Definition: APACHE-LICENSE-2.0.txt:10
MediaWiki\Storage\SlotRecord\getRevision
getRevision()
Returns the ID of the revision this slot is associated with.
Definition: SlotRecord.php:390
content
per default it will return the text for text based content
Definition: contenthandler.txt:104
MediaWiki\Storage\SlotRecord\base36Sha1
static base36Sha1( $blob)
Get the base 36 SHA-1 value for a string of text.
Definition: SlotRecord.php:564
array
the array() calling protocol came about after MediaWiki 1.4rc1.
MediaWiki\Storage\SlotRecord\getContent
getContent()
Returns the Content of the given slot.
Definition: SlotRecord.php:303
MediaWiki\Storage\SlotRecord\getModel
getModel()
Returns the content model.
Definition: SlotRecord.php:518
MediaWiki\Storage\SuppressedDataException
Exception raised in response to an audience check when attempting to access suppressed information wi...
Definition: SuppressedDataException.php:31
MediaWiki\Storage\SlotRecord\$row
object $row
database result row, as a raw object
Definition: SlotRecord.php:43
MediaWiki\Storage\SlotRecord\getStringField
getStringField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:360
MediaWiki\Storage\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:159
MediaWiki\Storage\SlotRecord\__sleep
__sleep()
Implemented to defy serialization.
Definition: SlotRecord.php:284
MediaWiki\Storage\SlotRecord\hasField
hasField( $name)
Definition: SlotRecord.php:381
MediaWiki\Storage\SlotRecord\$content
Content callable $content
Definition: SlotRecord.php:48
MediaWiki\Storage\SlotRecord\getFormat
getFormat()
Returns the blob serialization format as a MIME type.
Definition: SlotRecord.php:538
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:37
MediaWiki\Storage\SlotRecord\hasAddress
hasAddress()
Whether this slot has an address.
Definition: SlotRecord.php:429
$blob
$blob
Definition: testCompression.php:65
MediaWiki\Storage\SlotRecord\getAddress
getAddress()
Returns the address of this slot's content.
Definition: SlotRecord.php:459
MediaWiki\Storage\SlotRecord\newUnsaved
static newUnsaved( $role, Content $content)
Constructs a new Slot from a Content object for a new revision.
Definition: SlotRecord.php:124
MediaWiki\Storage\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:98
MediaWiki\Storage\SlotRecord\getRole
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:449
MediaWiki\Storage\SlotRecord\isInherited
isInherited()
Whether this slot was inherited from an older revision.
Definition: SlotRecord.php:414
$value
$value
Definition: styleTest.css.php:45
MediaWiki\Storage\SlotRecord\hasRevision
hasRevision()
Whether this slot has revision ID associated.
Definition: SlotRecord.php:440
MediaWiki\Storage\SlotRecord\newDerived
static newDerived(SlotRecord $slot, array $overrides=[])
Constructs a new SlotRecord from an existing SlotRecord, overriding some fields.
Definition: SlotRecord.php:75
MediaWiki\Storage
Definition: BlobAccessException.php:23
Content
Base interface for content objects.
Definition: Content.php:34
MediaWiki\Storage\SlotRecord\getIntField
getIntField( $name)
Returns the int value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:373
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
MediaWiki\Storage\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:58
MediaWiki\Storage\IncompleteRevisionException
Exception throw when trying to access undefined fields on an incomplete RevisionRecord.
Definition: IncompleteRevisionException.php:30
MediaWiki\Storage\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:38
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:22
MediaWiki\Storage\SlotRecord\getSize
getSize()
Returns the content size.
Definition: SlotRecord.php:479
Content\getModel
getModel()
Returns the ID of the content model used by this Content object.
MediaWiki\Storage\SlotRecord\getOrigin
getOrigin()
Returns the revision ID of the revision that originated the slot's content.
Definition: SlotRecord.php:399
MediaWiki\Storage\SlotRecord\getField
getField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
Definition: SlotRecord.php:330
MediaWiki\Storage\SlotRecord\__construct
__construct( $row, $content)
SlotRecord constructor.
Definition: SlotRecord.php:230
MediaWiki\Storage\SlotRecord\setField
setField( $name, $value)
Definition: SlotRecord.php:552