MediaWiki REL1_31
SlotRecord.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Storage;
24
25use Content;
26use InvalidArgumentException;
27use LogicException;
28use OutOfBoundsException;
29use Wikimedia\Assert\Assert;
30
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}
Exception throw when trying to access undefined fields on an incomplete RevisionRecord.
Value object representing a content slot associated with a page revision.
getRole()
Returns the role of the slot.
getContent()
Returns the Content of the given slot.
static newInherited(SlotRecord $slot)
Constructs a new SlotRecord for a new revision, inheriting the content of the given SlotRecord of a p...
getModel()
Returns the content model.
__construct( $row, $content)
SlotRecord constructor.
isInherited()
Whether this slot was inherited from an older revision.
getField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
getFormat()
Returns the blob serialization format as a MIME type.
getIntField( $name)
Returns the int value of a data field from the database row supplied to the constructor.
getAddress()
Returns the address of this slot's content.
Content callable $content
hasRevision()
Whether this slot has revision ID associated.
static newWithSuppressedContent(SlotRecord $slot)
Returns a new SlotRecord just like the given $slot, except that calling getContent() will fail with a...
static newSaved( $revisionId, $contentId, $contentAddress, SlotRecord $protoSlot)
Constructs a complete SlotRecord for a newly saved revision, based on the incomplete proto-slot.
getRevision()
Returns the ID of the revision this slot is associated with.
object $row
database result row, as a raw object
getSha1()
Returns the content size.
getSize()
Returns the content size.
hasAddress()
Whether this slot has an address.
static newDerived(SlotRecord $slot, array $overrides=[])
Constructs a new SlotRecord from an existing SlotRecord, overriding some fields.
getStringField( $name)
Returns the string value of a data field from the database row supplied to the constructor.
getContentId()
Returns the ID of the content meta data row associated with the slot.
__sleep()
Implemented to defy serialization.
getOrigin()
Returns the revision ID of the revision that originated the slot's content.
static newUnsaved( $role, Content $content)
Constructs a new Slot from a Content object for a new revision.
static base36Sha1( $blob)
Get the base 36 SHA-1 value for a string of text.
Exception raised in response to an audience check when attempting to access suppressed information wi...
Base interface for content objects.
Definition Content.php:34
getModel()
Returns the ID of the content model used by this Content object.