Do Polymorphic Relationships Work in 4.1?


Do polymorphic relationships work in Silverstripe V4.1?

I have two classes (Client & Campaign) that both have many of a third class (Value). My thought was that I could set up a polymorphic relationship so that class Value has a has_one relationship to a data object and class Client and class Campaign has a has_many relationship to class Value.

The relevant code snippet is below.

Everything seems to work, model admin allows me to add value items to both the other classes, however, on querying them ($this->Values()->Count()), they result in zero items found.

Looking at the Value database record in PHPMyAdmin, it would appear that the fields for ValueOfClass and ValueOfID are created to support the relationship but they are not set.

The code is created based on the documentation at Relations between Records – SilverStripe Documentation

Any help would be appreciated as I have been struggling with this all day!

class Client extends DataObject {
	private static $table_name = 'Client';
	private static $has_many = [
		"Values" => "Value.ValueOf"

class Campaign extends DataObject {
	private static $table_name = 'Campaign';
	private static $has_many = [
		"Values" => "Value.ValueOf"

class Value extends DataObject {
	private static $table_name = 'Value';
	private static $has_one = [
		"ValueOf" => DataObject::class

Your Value class isn’t defining a has_one in your example - I don’t know if that’s a typo or a copy of your code, so may or may not be related.

How are you linking the records? Is it via the CMS or via code?

If via code can you show us how you’re doing it? If via the CMS, how are you picking the objects, can we see the Form code?


The has_one was a typo as I had actually given up trying and coded the solution a different way, however, it would be good to know a solution as I can always revert back.

What I was trying to achive was a way of copying the values from a Client record to a Campaign record and was doing everything via model admin. As an example I was capturing the values on the client record which was via automatic scaffolding in the getCMSFields, which worked fine.

Attempted data retrieval was on the Campaign record when a new record is created in ModelAdmin as follows:

public function onBeforeWrite() {
	if($this->ID == 0)
		foreach($this->Client()->Values() as $value)
			$duplicate = $value->createDuplicate();

The problem was that $this->Client()->Values() was returning 0 records.

I am still using the same code above but I have now used a seperate class for Client Value and Campaign Value and the code works fine.

Hope that makes sense?

Where does $this->Client() come from?

Also, in onBeforeWrite() this could be possible, that the relation is not available when the DataObject is not written. You can try to put this in onAfterWrite(), as you only modify the current DataObjects relations and don’t neet to write the current DO after changing stuff.

If you need it just to fire after you write it the first time you can set a flag inside onBeforeWrite where you can check if it’s not in DB. I prefer to use $this->isInDB() which does the same like “ID == 0”, but is more readable. Then in onAfterWrite() you can check if the flag was set and sync your relations.

$this->Client() is a has_one relationship set up on the campaign record and the clientID is set.

The relation between Client and Campaign is definately there in the onBeforeWrite() as I can see the other information in the Client record, just not the Values()

I will try moving the code to the onAfterWrite and see if that makes any difference.

I like the $this->isInDB idea!