Programmatically append multiple paragraphs to entity reference field on node

I have a paragraph type that contains a text field and a text format field. I also have a content type that takes an unlimited number of paragraphs of this type.

The paragraph type is called 'questions_and_answers' and the two fields are 'field_question' and 'field_answer'. I'd like to be able to programmatically append 'questions_and_answers' paragraphs to a content type.

In the example below I'm grabbing the content from a 'double' field and importing them into the paragraph reference field. Here's my code so far.

    $nids = \Drupal::entityQuery('node')
        ->condition('type', 'faq')
        ->execute();
    foreach ($nids as $nid) {
        $node      = \Drupal\node\Entity\NODE::load($nid);
        $questions = $node->field_questions_and_answers->getValue();
        foreach ($questions as $question) {
            $q         = $question['first'];
            $a         = $question['second'];
            $paragraph = Paragraph::create([
                    'title'          => $q,
                    'type'           => 'questions_and_answers',
                    'field_question' => $q,
                    'field_answer'   => $a,
                ]);
            $paragraph->save();
            $node->field_qs_and_as[] = $paragraph->id();
        }
        $node->save();
    }

However when I perform this action the node gets a number of question paragraphs added but they're completely empty and they can't even be deleted. What am I missing? How do you append multiple paragraphs you created to an entity reference field on a node?

Answers 3

  • When you add a paragraph you also need to pass the revision id:

      $node->field_qs_and_as[] = [
        'target_id' => $paragraph->id(),
        'target_revision_id' => $paragraph->getRevisionId(),
      ];
    

  • I've found it preferable to pass the entity object instead of manually setting the IDs yourself:

    $node->field_qs_and_as[] = $paragraph;
    $node->save();
    

    Only make one call to $node->save() of course, after you've added/edited all the paragraph entities you want.

    The primary reason for this is that when I set the reference array values instead, at least in the case of a newly created paragraph, the paragraph does not end up with a parent entity set. If you later need to call $paragraph->getParentEntity() or otherwise make use of Paragraphs' ability to trace the relationship in the reverse direction, that will be a problem.

    I haven't looked at the code paths but it makes sense that if you call ->save() on the Paragraph it will not be able to detect a parent on its own. I would imagine the Entity Reference Revisions code handling array values then assumes that the relationship must already established on the child entity, if it's being passed existing IDs instead of an object.


  • I spent hours make it work, the problem was when adding a paragraph to my multivalues node field, the existing values were lost. I finally came to this working solution (retrieving existing paragraphs and adding them back together with the new paragraph), but I feel I'm missing something...

    $currentUser = \Drupal\user\Entity\User::load(\Drupal::currentUser()->id());
    $product = Node::load($form_state->getValue('product_id'));
    
    //create new ordered_item paragraph
    $order = Paragraph::create(['type' =>'item_ordered']);
    $order->field_item_ordered_number = $form_state->getValue('quantity');
    $order->field_item_ordered_product = $product->id();
    $order->uid = $currentUser->id();
    $order->save();
    
    $order_items[] = [
    'target_id' => $order->id(),
    'target_revision_id' => $order->getRevisionId(),
    ];
    
    //check if user has already a cart
    $cart=\Drupal::service('dpnews_home.get_user_infos')->getUserCart($currentUser);
    
    foreach ($cart->field_ordered_item as $item) {
      $existing_order=$item->entity;
      $order_items[] = [
      'target_id' => $existing_order->id(),
      'target_revision_id' => $existing_order->getRevisionId(),
      ];
    }
    //if not create new cart for user
    if(!isset($cart)){
      $cart = Node::create(['type' => 'panier']);
      $cart->uid = $currentUser->id();
      $cart->title= 'Panier en cours - utilisateur '.$currentUser->id();
      $cart->save();
    }
    
    //add ordered item to cart
    $cart->set('field_ordered_item',$order_items);
    $cart->save();
    

Related Questions