Skip to content


BUG Fix DataObject / Versioned publishing issues #2917

merged 1 commit into from

3 participants


This issue was initially raised at and fixes silverstripe/silverstripe-blog#105.

After investigation I found the following issues.

If a DataObject was queried under a certain stage or reading mode, and then this mode changes, lazy-loading of fields would fail since it will always attempt to use the current mode. This is fixed in DataObject::loadLazyFields so that any DataObject will maintain it's own query parameters internally, propegating this context from initial load all the way to lazy loading of fields.

This fix was made in DataObject rather than in Versioned::augmentLoadLazyFields because (in my humble opinion) it's the role of the DataObject to maintain its own query context, and it should only be touched by extensions to modify this explicitly. This is easier than updating each dataobject extension across the board.

Secondly, Versioned::writeToStage was failing if no fields were modified, meaning simply loading a record from one stage and writing it to another would result in no write performed.

Test cases were included in VersionedTest rather than DataObjectLazyLoadingTest becuase I feel that this is more a test of the Versioned extension (and DataObject's support for extensions) than the lazy loading process itself. I could just as easy have put it there though. :)

I've marked this as "don't merge" because I'd like to get a few developer opinions before I'm confident this is the correct solution.

@tractorcow tractorcow BUG Fix DataObject::loadLazyFields discarding original query parameters
BUG Fix Versioned::writeToStage failing to write object with unchanged fields

@sunnysideup would you like to peer-review my PR? :)


Sure, what is the best way to do this? review the code or how it works or both etc...???


I just wonder if my thinking is correct in the getSourceQueryParams part of the code; If a dataobject is queried, is it wise to do a blind restore of the original query parameters when lazy loading?


Removed the "don't merge" - I think this can go into core as long as it's checked by another developer before they merge it.


I just had a first look. It seems to make sense.

what if Query Params are already set, will it double-up or have too many query params? I am not sure if that is even relevant, but just wanted to check. I dont think this is an issue - and I think you are basically wondering about the same thing. I just wonder how I can even test this.

because it such a CORE CORE change, would it be worthwhile to work out how much longer it takes to run the code to avoid it slowing down EVERYTHING? This would be fairly easy to test and it is important to know.


Thanks for the review. :)

QueryParams are already set in the DataQuery constructor to the current defaults, such as versioned stage, etc. The code above simply overwrites any default keys with those saved on the DataObject, so it won't remove any keys. This might cause problems if any extension relies on the absence of any query param to work...

I don't think it's going to be a performance hit; It's just copying variables from one object into the DataQuery.


I saw an old issue that this fixes too. I guess I'll have to go find it now :p

@simonwelsh simonwelsh merged commit 90ba514 into silverstripe:3.1

1 check passed

Details default Scrutinizer: No new issues — Travis: Passed
@tractorcow tractorcow deleted the tractorcow:pulls/fix-lazyload-queryparams branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 4, 2014
  1. @tractorcow

    BUG Fix DataObject::loadLazyFields discarding original query parameters

    tractorcow committed
    BUG Fix Versioned::writeToStage failing to write object with unchanged fields
Showing with 47 additions and 0 deletions.
  1. +5 −0 model/DataObject.php
  2. +1 −0 model/Versioned.php
  3. +41 −0 tests/model/VersionedTest.php
5 model/DataObject.php
@@ -2166,6 +2166,11 @@ protected function loadLazyFields($tableClass = null) {
$dataQuery = new DataQuery($tableClass);
+ // Reset query parameter context to that of this DataObject
+ if($params = $this->getSourceQueryParams()) {
+ foreach($params as $key => $value) $dataQuery->setQueryParam($key, $value);
+ }
// TableField sets the record ID to "new" on new row data, so don't try doing anything in that case
if(!is_numeric($this->record['ID'])) return false;
1 model/Versioned.php
@@ -1184,6 +1184,7 @@ public function writeToStage($stage, $forceInsert = false) {
$oldMode = Versioned::get_reading_mode();
+ $this->owner->forceChange();
$result = $this->owner->write(false, $forceInsert);
41 tests/model/VersionedTest.php
@@ -492,6 +492,47 @@ public function testVersionedWithSingleStage() {
'Writes to and reads from default stage even if a non-matching stage is set'
+ /**
+ * Test that publishing processes respects lazy loaded fields
+ */
+ public function testLazyLoadFields() {
+ $originalMode = Versioned::get_reading_mode();
+ // Generate staging record and retrieve it from stage in live mode
+ Versioned::reading_stage('Stage');
+ $obj = new VersionedTest_Subclass();
+ $obj->Name = 'bob';
+ $obj->ExtraField = 'Field Value';
+ $obj->write();
+ $objID = $obj->ID;
+ $filter = sprintf('"VersionedTest_DataObject"."ID" = \'%d\'', Convert::raw2sql($objID));
+ Versioned::reading_stage('Live');
+ // Check fields are unloaded prior to access
+ $objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false);
+ $lazyFields = $objLazy->getQueriedDatabaseFields();
+ $this->assertTrue(isset($lazyFields['ExtraField_Lazy']));
+ $this->assertEquals('VersionedTest_Subclass', $lazyFields['ExtraField_Lazy']);
+ // Check lazy loading works when viewing a Stage object in Live mode
+ $this->assertEquals('Field Value', $objLazy->ExtraField);
+ // Test that writeToStage respects lazy loaded fields
+ $objLazy = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Stage', $filter, false);
+ $objLazy->writeToStage('Live');
+ $objLive = Versioned::get_one_by_stage('VersionedTest_DataObject', 'Live', $filter, false);
+ $liveLazyFields = $objLive->getQueriedDatabaseFields();
+ // Check fields are unloaded prior to access
+ $this->assertTrue(isset($liveLazyFields['ExtraField_Lazy']));
+ $this->assertEquals('VersionedTest_Subclass', $liveLazyFields['ExtraField_Lazy']);
+ // Check that live record has original value
+ $this->assertEquals('Field Value', $objLive->ExtraField);
+ Versioned::set_reading_mode($originalMode);
+ }
Something went wrong with that request. Please try again.