mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API Add a CREATE_MEMORY_HYDRATED option to DataObject constructor (#9767)
This commit is contained in:
parent
0dabdbfa41
commit
9ca33950a2
@ -199,10 +199,17 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
|
||||
/**
|
||||
* Value for 2nd argument to constructor, indicating that a record is being hydrated from the database
|
||||
* Neither setters and nor default population will be called
|
||||
* Setter methods are not called, and population via private static $defaults will not occur.
|
||||
*/
|
||||
const CREATE_HYDRATED = 2;
|
||||
|
||||
/**
|
||||
* Value for 2nd argument to constructor, indicating that a record is being hydrated from memory. This can be used
|
||||
* to initialised a record that doesn't yet have an ID. Setter methods are not called, and population via private
|
||||
* static $defaults will not occur.
|
||||
*/
|
||||
const CREATE_MEMORY_HYDRATED = 3;
|
||||
|
||||
/**
|
||||
* An array indexed by fieldname, true if the field has been changed.
|
||||
* Use {@link getChangedFields()} and {@link isChanged()} to inspect
|
||||
@ -339,7 +346,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
* Construct a new DataObject.
|
||||
*
|
||||
* @param array $record Initial record content, or rehydrated record content, depending on $creationType
|
||||
* @param int|boolean $creationType Set to DataObject::CREATE_OBJECT, DataObject::CREATE_HYDRATED, or DataObject::CREATE_SINGLETON. Used by SilverStripe internals as best left as the default by regular users.
|
||||
* @param int|boolean $creationType Set to DataObject::CREATE_OBJECT, DataObject::CREATE_HYDRATED,
|
||||
* DataObject::CREATE_MEMORY_HYDRATED or DataObject::CREATE_SINGLETON. Used by Silverstripe internals and best
|
||||
* left as the default by regular users.
|
||||
* @param array $queryParams List of DataQuery params necessary to lazy load, or load related objects.
|
||||
*/
|
||||
public function __construct($record = [], $creationType = self::CREATE_OBJECT, $queryParams = [])
|
||||
@ -366,31 +375,10 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$this->record = [];
|
||||
|
||||
switch ($creationType) {
|
||||
// Hydrate a record from the database
|
||||
// Hydrate a record
|
||||
case self::CREATE_HYDRATED:
|
||||
if (!is_array($record) || empty($record['ID'])) {
|
||||
throw new \InvalidArgumentException("Hydrated records must be passed a record array including an ID");
|
||||
}
|
||||
|
||||
$this->record = $record;
|
||||
|
||||
// Identify fields that should be lazy loaded, but only on existing records
|
||||
// Get all field specs scoped to class for later lazy loading
|
||||
$fields = static::getSchema()->fieldSpecs(
|
||||
static::class,
|
||||
DataObjectSchema::INCLUDE_CLASS | DataObjectSchema::DB_ONLY
|
||||
);
|
||||
foreach ($fields as $field => $fieldSpec) {
|
||||
$fieldClass = strtok($fieldSpec, ".");
|
||||
if (!array_key_exists($field, $record)) {
|
||||
$this->record[$field . '_Lazy'] = $fieldClass;
|
||||
}
|
||||
}
|
||||
|
||||
$this->original = $this->record;
|
||||
$this->changed = [];
|
||||
$this->changeForced = false;
|
||||
|
||||
case self::CREATE_MEMORY_HYDRATED:
|
||||
$this->hydrate($record, $creationType === self::CREATE_HYDRATED);
|
||||
break;
|
||||
|
||||
// Create a new object, using the constructor argument as the initial content
|
||||
@ -449,6 +437,43 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor hydration logic for CREATE_HYDRATED and CREATE_MEMORY_HYDRATED.
|
||||
* @param array $record
|
||||
* @param bool $mustHaveID If true, an exception will be thrown if $record doesn't have an ID.
|
||||
*/
|
||||
private function hydrate(array $record, bool $mustHaveID)
|
||||
{
|
||||
if ($mustHaveID && empty($record['ID'])) {
|
||||
// CREATE_HYDRATED requires an ID to be included in the record
|
||||
throw new \InvalidArgumentException(
|
||||
"Hydrated records must be passed a record array including an ID."
|
||||
);
|
||||
} elseif (empty($record['ID'])) {
|
||||
// CREATE_MEMORY_HYDRATED implicitely set the record ID to 0 if not provided
|
||||
$record['ID'] = 0;
|
||||
}
|
||||
|
||||
$this->record = $record;
|
||||
|
||||
// Identify fields that should be lazy loaded, but only on existing records
|
||||
// Get all field specs scoped to class for later lazy loading
|
||||
$fields = static::getSchema()->fieldSpecs(
|
||||
static::class,
|
||||
DataObjectSchema::INCLUDE_CLASS | DataObjectSchema::DB_ONLY
|
||||
);
|
||||
foreach ($fields as $field => $fieldSpec) {
|
||||
$fieldClass = strtok($fieldSpec, ".");
|
||||
if (!array_key_exists($field, $record)) {
|
||||
$this->record[$field . '_Lazy'] = $fieldClass;
|
||||
}
|
||||
}
|
||||
|
||||
$this->original = $this->record;
|
||||
$this->changed = [];
|
||||
$this->changeForced = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all of this objects dependant objects and local caches.
|
||||
* You'll need to call this to get the memory of an object that has components or extensions freed.
|
||||
@ -750,7 +775,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
|
||||
$originalClass = $this->ClassName;
|
||||
|
||||
/** @var DataObject $newInstance */
|
||||
$newInstance = Injector::inst()->create($newClassName, $this->record, self::CREATE_HYDRATED);
|
||||
$newInstance = Injector::inst()->create($newClassName, $this->record, self::CREATE_MEMORY_HYDRATED);
|
||||
|
||||
// Modify ClassName
|
||||
if ($newClassName != $originalClass) {
|
||||
|
@ -1669,6 +1669,31 @@ class DataObjectTest extends SapphireTest
|
||||
$dataObject->newClassInstance('Controller');
|
||||
}
|
||||
|
||||
public function testNewClassInstanceFromUnsavedDataObject()
|
||||
{
|
||||
$dataObject = new DataObjectTest\Team([
|
||||
'Title' => 'Team 1'
|
||||
]);
|
||||
$changedDO = $dataObject->newClassInstance(DataObjectTest\SubTeam::class);
|
||||
$changedFields = $changedDO->getChangedFields();
|
||||
|
||||
// Don't write the record, it will reset changed fields
|
||||
$this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
|
||||
$this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
|
||||
$this->assertEquals($changedDO->RecordClassName, DataObjectTest\SubTeam::class);
|
||||
$this->assertContains('ClassName', array_keys($changedFields));
|
||||
$this->assertEquals($changedFields['ClassName']['before'], DataObjectTest\Team::class);
|
||||
$this->assertEquals($changedFields['ClassName']['after'], DataObjectTest\SubTeam::class);
|
||||
$this->assertEquals($changedFields['RecordClassName']['before'], DataObjectTest\Team::class);
|
||||
$this->assertEquals($changedFields['RecordClassName']['after'], DataObjectTest\SubTeam::class);
|
||||
|
||||
$changedDO->write();
|
||||
|
||||
$this->assertInstanceOf(DataObjectTest\SubTeam::class, $changedDO);
|
||||
$this->assertEquals($changedDO->ClassName, DataObjectTest\SubTeam::class);
|
||||
$this->assertNotEmpty($changedDO->ID, 'New class instance got an ID generated on write');
|
||||
}
|
||||
|
||||
public function testMultipleManyManyWithSameClass()
|
||||
{
|
||||
$team = $this->objFromFixture(DataObjectTest\Team::class, 'team1');
|
||||
@ -2561,8 +2586,22 @@ class DataObjectTest extends SapphireTest
|
||||
'Salary' => 50,
|
||||
], DataObject::CREATE_HYDRATED);
|
||||
$this->assertEquals(null, $staff->EmploymentType);
|
||||
$this->assertEquals(DataObjectTest\Staff::class, $staff->ClassName);
|
||||
$this->assertEquals([], $staff->getChangedFields());
|
||||
|
||||
// Test hydration (DataObject::CREATE_HYDRATED)
|
||||
// Defaults are not used, changes are not tracked
|
||||
$staff = new DataObjectTest\Staff([
|
||||
'Salary' => 50,
|
||||
], DataObject::CREATE_MEMORY_HYDRATED);
|
||||
$this->assertEquals(DataObjectTest\Staff::class, $staff->ClassName);
|
||||
$this->assertEquals(null, $staff->EmploymentType);
|
||||
$this->assertEquals([], $staff->getChangedFields());
|
||||
$this->assertFalse(
|
||||
$staff->isInDB(),
|
||||
'DataObject hydrated from memory without an ID are assumed to not be in the Database.'
|
||||
);
|
||||
|
||||
// Test singleton (DataObject::CREATE_SINGLETON)
|
||||
// Values are ingored
|
||||
$staff = new DataObjectTest\Staff([
|
||||
@ -2572,4 +2611,15 @@ class DataObjectTest extends SapphireTest
|
||||
$this->assertEquals(null, $staff->Salary);
|
||||
$this->assertEquals([], $staff->getChangedFields());
|
||||
}
|
||||
|
||||
public function testDataObjectCreationHydrateWithoutID()
|
||||
{
|
||||
$this->expectExceptionMessage(
|
||||
"Hydrated records must be passed a record array including an ID."
|
||||
);
|
||||
// Hydrating a record without an ID should throw an exception
|
||||
$staff = new DataObjectTest\Staff([
|
||||
'Salary' => 50,
|
||||
], DataObject::CREATE_HYDRATED);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user