MediaWiki REL1_33
SlotRecord.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Revision;
24
26use InvalidArgumentException;
27use LogicException;
28use OutOfBoundsException;
29use Wikimedia\Assert\Assert;
30
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
663class_alias( SlotRecord::class, 'MediaWiki\Storage\SlotRecord' );
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Exception throw when trying to access undefined fields on an incomplete RevisionRecord.
Value object representing a content slot associated with a page revision.
getContent()
Returns the Content of the given slot.
hasSameContent(SlotRecord $other)
Returns true if $other has the same content as this slot.
getRole()
Returns the role of the slot.
static newInherited(SlotRecord $slot)
Constructs a new SlotRecord for a new revision, inheriting the content of the given SlotRecord of a p...
hasAddress()
Whether this slot has an address.
getSha1()
Returns the content size.
getSize()
Returns the content size.
__sleep()
Implemented to defy serialization.
getAddress()
Returns the address of this slot's content.
__construct( $row, $content)
The following fields are supported by the $row parameter:
hasOrigin()
Whether this slot has an origin (revision ID that originated the slot's content.
hasRevision()
Whether this slot has revision ID associated.
getModel()
Returns the content model.
static base36Sha1( $blob)
Get the base 36 SHA-1 value for a string of text.
isInherited()
Whether this slot was inherited from an older revision.
getOrigin()
Returns the revision ID of the revision that originated the slot's content.
static newSaved( $revisionId, $contentId, $contentAddress, SlotRecord $protoSlot)
Constructs a complete SlotRecord for a newly saved revision, based on the incomplete proto-slot.
hasContentId()
Whether this slot has a content ID.
static newWithSuppressedContent(SlotRecord $slot)
Returns a new SlotRecord just like the given $slot, except that calling getContent() will fail with a...
getContentId()
Returns the ID of the content meta data row associated with the slot.
getRevision()
Returns the ID of the revision this slot is associated with.
object $row
database result row, as a raw object.
getStringField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
getField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
static newUnsaved( $role, Content $content)
Constructs a new Slot from a Content object for a new revision.
getFormat()
Returns the blob serialization format as a MIME type.
static newDerived(SlotRecord $slot, array $overrides=[])
Constructs a new SlotRecord from an existing SlotRecord, overriding some fields.
getIntField( $name)
Returns the int value of a data field from the database row supplied to the constructor.
Exception raised in response to an audience check when attempting to access suppressed information wi...
per default it will return the text for text based content
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
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:271
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
Base interface for content objects.
Definition Content.php:34
getModel()
Returns the ID of the content model used by this Content object.
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))