diff --git a/src/ORM/ManyManyList.php b/src/ORM/ManyManyList.php index 9ba18e5ff..5ad69cd8f 100644 --- a/src/ORM/ManyManyList.php +++ b/src/ORM/ManyManyList.php @@ -433,6 +433,58 @@ class ManyManyList extends RelationList } } + /** + * Set the extra field data for a single row of the relationship join + * table, given the known child ID. + * + * @param int $itemID The ID of the child for the relationship + * @param array $data The data to set, with field names as keys and values as values + * @throws InvalidArgumentException if the $data array is invalid + */ + public function setExtraData(int $itemID, array $data): void + { + // Don't bother doing anything if we aren't given any data + if (empty($data)) { + return; + } + + // Prepare db manipulation + $foreignID = $this->getForeignID(); + $manipulation = [ + $this->joinTable => [ + 'command' => 'update', + 'fields' => [], + 'where' => [ + "\"{$this->joinTable}\".\"{$this->foreignKey}\"" => $foreignID, + "\"{$this->joinTable}\".\"{$this->localKey}\"" => $itemID + ], + ], + ]; + + /** @var DBField[] $fieldObjects */ + $fieldObjects = []; + // Write extra field to manipluation in the same way + // that DataObject::prepareManipulationTable writes fields + foreach ($data as $fieldName => $value) { + if (!array_key_exists($fieldName, $this->extraFields)) { + throw new InvalidArgumentException("Field '$fieldName' is not defined in many_many_extraFields for this relationship"); + } + $fieldObject = Injector::inst()->create($this->extraFields[$fieldName], $fieldName); + // Make sure the field assignment is not an array unless the field allows non-scalar values + if (is_array($value) && $fieldObject->scalarValueOnly()) { + throw new InvalidArgumentException( + 'ManyManyList::setExtraData: parameterised field assignments are disallowed' + ); + } + // Set the value into the manipulation + $fieldObject->setValue($value); + $fieldObject->writeToManipulation($manipulation[$this->joinTable]); + $fieldObjects[$fieldName] = $fieldObject; + } + + DB::manipulate($manipulation); + } + /** * Find the extra field data for a single row of the relationship join * table, given the known child ID. diff --git a/tests/php/ORM/ManyManyListTest.php b/tests/php/ORM/ManyManyListTest.php index e3475ef31..f7b4dd5b5 100644 --- a/tests/php/ORM/ManyManyListTest.php +++ b/tests/php/ORM/ManyManyListTest.php @@ -11,7 +11,6 @@ use SilverStripe\ORM\Tests\DataObjectTest\Player; use SilverStripe\ORM\Tests\DataObjectTest\Team; use SilverStripe\ORM\Tests\ManyManyListTest\ExtraFieldsObject; use SilverStripe\ORM\Tests\ManyManyListTest\Product; -use InvalidArgumentException; class ManyManyListTest extends SapphireTest { @@ -73,7 +72,7 @@ class ManyManyListTest extends SapphireTest // the actual test is that this does not generate an error in the sql. $obj->Products()->add($product, [ - 'Reference' => 'Foo' + 'Reference' => 'Foo', ]); $result = $obj->Products()->First(); @@ -81,6 +80,36 @@ class ManyManyListTest extends SapphireTest $this->assertEquals('Test Product', $result->Title); } + public function testSetExtraData() + { + $obj = new ManyManyListTest\ExtraFieldsObject(); + $obj->write(); + + $obj2 = new ManyManyListTest\ExtraFieldsObject(); + $obj2->write(); + + $money = new DBMoney(); + $money->setAmount(100); + $money->setCurrency('USD'); + + // Set some data on add + $obj->Clients()->add($obj2, [ + 'Worth' => $money, + 'Reference' => 'Foo', + ]); + // Change the data afterward + $money->setAmount(50); + $obj->Clients()->setExtraData($obj2->ID, [ + 'Worth' => $money, + 'Reference' => 'Bar', + ]); + + $result = $obj->Clients()->First(); + $this->assertEquals('Bar', $result->Reference, 'Basic scalar fields should exist'); + $this->assertInstanceOf(DBMoney::class, $result->Worth, 'Composite fields should exist on the record'); + $this->assertEquals(50, $result->Worth->getAmount()); + } + public function testCreateList() { $list = ManyManyList::create(