Setting the Target ID on a Taxonomy Reference Field in Drupal

Recently on a Drupal website, I was tasked with adding a taxonomy reference field to a content type. This taxonomy reference field replaced an existing select list. I was also tasked with writing a script to programmatically assign the value of the existing select field to the new taxonomy reference field.

But how do you programmatically set the value for a taxonomy reference field? It should be pretty straightforward and usually is. Most examples you find online use one of these two methods:

$node_object->field_taxonomy_reference->target_id = 1234;
$node_object->set('field_taxonomy_reference', 1234);

Both these methods work fine if the taxonomy reference field has already been populated through the UI--in other words, if the field already has a value, and you just need to change that value. But in my situation, none of the nodes had a value in the new taxonomy reference field--they were all empty. And that was a problem, because neither of these two methods work with a completely empty taxonomy reference field. If you try this:

$node_object->field_taxonomy_reference->target_id = 1234;

you will get an error like this:

Drupal\Core\Entity\EntityStorageException: The "" entity type does not exist. in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (line 811 of core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).

And if you try this:

$node_object->set('field_taxonomy_reference', 1234);

you will get an error like this:

InvalidArgumentException: Value is not a valid entity. in Drupal\Core\Entity\Plugin\DataType\EntityReference->setValue() (line 125 of core/lib/Drupal/Core/Entity/Plugin/DataType/EntityReference.php).

It doesn't help if you specify the target_id in an array, either, like this:

$node_object->set('field_taxonomy_reference', ['target_id' => 1234]);

which gives you this error:

InvalidArgumentException: No entity type was provided, value is not a valid entity. in Drupal\dynamic_entity_reference\Plugin\Field\FieldType\DynamicEntityReferenceItem->setValue() (line 422 of modules/contrib/dynamic_entity_reference/src/Plugin/Field/FieldType/DynamicEntityReferenceItem.php).

All these errors hint at the problem, but figuring out the solution wasn't exactly obvious, at least to me. These errors all indicate that, at this point, without this field having been populated yet at all, Drupal doesn't know what kind of entity it is, and therefore, it can't assign a value to it. This problem is clearest in the last example above, where it reads, "No entity type was provided." If you loop through the field_taxonomy_reference object when it is not populated yet, you will find that $node_object->field_resource_type_taxonomy doesn't show anything. If you loop through that field when it is populated, you can see that

$node_object->field_resource_type_taxonomy[0] = ['target_id' => 2533, 'target_type' => 'taxonomy_term']

And this is the missing piece:  we need to specify the target_type.  None of the examples I have found on the web provide this solution, but it's needed if the target field is unpopulated.  Now if we adjust the two examples we started out with, we can try:

$node_object->field_taxonomy_reference->target_type = 'taxonomy_term';

$node_object->field_taxonomy_reference->target_id = 1234;

and

$node_object->set('field_taxonomy_reference', ['target_id' => 1234, 'target_type' => 'taxonomy_term']);

And this time it works, because we have specified the entity type, which is, in this case, the same as the target_type. And once this value has been set, we don't need to specify the target type again, and the original examples will work.

Interestingly, if we the field is already populated, this command will not work:

$node_object->set('field_taxonomy_reference', ['target_id' => 1234]);

but this one will:

$node_object->set('field_taxonomy_reference', ['target_id' => 1234, 'target_type' => 'taxonomy_term']);

In this one case, we still need to specify the target_type along with the target_id. However, since we might not know if the field we are targeting has a value yet or not, it is worthwhile to specify the target type, anyway. It also would help with the clarity and readability of the code; finally, including the target_type whether the target field is populated or not will help us remember that the target_type is needed if the the field has no value.