MediaWiki REL1_32
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
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
665class_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)
SlotRecord constructor.
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
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:302
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))