Drupal Techniques: Creating nodes by code

Written on May 17, 2020

Category: Drupal Techniques

Author: David Rodríguez, @davidjguru

Picture from Unsplash, by @manuelsardo
Picture from Unsplash, user Manuel Sardo, @manuelsardo

Last week I needed to develop some functionality for a project and I did something I hadn’t implemented for a long time: I had to create nodes through programming and so I realized some things that had been deprecated at this point, So I take this opportunity to publish a simple snippet about this.

Creating a custom module

First of all I’ve created a new custom module to gather these snippets, called: creating_nodes using drupal console:

drupal generate:module \
--module="Creating Nodes
--machine-name="creating_nodes
--module-path="modules/custom" \
--description="Creating nodes in Drupal 8|9 programmatically" \
--core="8.x" \
--package="Workshop Drupal"
--module-file \
--no-interaction

Creating a basic scaffolding for a node

Well, then I’ve created a .install file in order to use the hook_install() for testing the node creation, just like this:

function creating_nodes_install() {

  // Ask for the current time.
  $requested_time = \Drupal::time()->getRequestTime();

 // Define basic values for the new node.
  $node_article = Node::create([
    'type' => 'article',
    'langcode' => 'en',
    'created' => $requested_time,
    'changed' => $requested_time,
    'uid' => 1,
    'title' => 'Article number one',
  ]);

  // Save the node.
  $node_article->save();
}

Now, enabling the module by Drush using: drush en creating_nodes you can see in path: /admin/content the new created node in your Drupal installation:

New node article just created

Creating taxonomy terms for the node

But then I wanted some taxonomy terms, so I did:

// Create two taxonomy terms.
  $term_one = Term::create([
    'vid' => 'tags',
    'langcode' => 'en',
    'name' => 'T_1',
    'description' => [
      'value' => '<p>Term number one.</p>',
      'format' => 'full_html',
    ],
    'weight' => -1,
    'parent' => [0],
  ]);
  $term_two = Term::create([
    'vid' => 'tags',
    'langcode' => 'en',
    'name' => 'T_2',
    'description' => [
      'value' => '<p>Term number two.</p>',
      'format' => 'full_html',
    ],
    'weight' => 1,
    'parent' => [0],
  ]);

  // Saving the taxonomy terms.
  $term_one->save();
  $term_two->save();

  // Get all the terms from a taxonomy.
  $terms = \Drupal::entityTypeManager()->getStorage('taxonomy_term')->loadTree('tags', 0, NULL, FALSE);

Now, $terms is a just an array of partial objects (not the whole Entity, very heavy-weight for testing) based in Taxonomy Term Entity model. See TermStorage::loadTree. And so, I can load a term as a tag in my testing node. I will add also a text for the body:

// Building the new node.
  $node_article = Node::create([
    'type' => 'article',
    'langcode' => 'en',
    'created' => $requested_time,
    'changed' => $requested_time,
    'uid' => 1,
    'title' => 'Article number one',
    'field_tags' => $terms[1]->tid,
    'body' => [
      'summary' => 'Summary for the node created programmatically.',
      'value' => "This is the body of the node <br> allows HTML tags if needed.",
      'format' => 'full_html',
    ],
  ]);

  // Save the node.
  $node_article->save();

Creating an image for the node

Following with the example case, now I have to add an image to the node. First a need an available image inside my public path for files, within my Drupal installation, so I’ll put an image on my /files folder:

Adding an image file to the node

Existing now the referenced image, I can build a file using the resource:

// Create a file.
  $file = File::create([
    'uid' => 1,
    'filename' => 'drupalea.png',
    'uri' => 'public://creating_nodes_files/drupalea.png',
    'status' => 1,
  ]);

  // Save the file item.
  $file->save();

So now my node is being well-formed:

// Building the new node.
  $node_article = Node::create([
    'type' => 'article',
    'langcode' => 'en',
    'created' => $requested_time,
    'changed' => $requested_time,
    'uid' => 1,
    'title' => 'Article number one',
    'field_tags' => $terms[1]->tid,
    'body' => [
      'summary' => 'Summary for the node created programmatically.',
      'value' => "This is the body of the node <br> allows HTML tags if needed.",
      'format' => 'full_html',
    ],
    'field_image' => [
      [
        'target_id' => $file->id(),
        'alt' => 'Alt text for the image',
        'title' => 'Title for the image',
      ],
    ],
  ]);

  // Save the node.
  $node_article->save();

Ok, the node goes well, but I think I need load a custom path, so let’s go to create it.

Creating paths for the node

Okay, I got some surprises here. As I remembered, to create paths I can use some like this:

\Drupal::service('path.alias_storage')->save("/node/" . $node->id(), "/newsletter", "en");

But doesn’t work…why? well, seems some deprecated…
See:

So I did it with two options:

1-Using pathauto module: You have patterns for the content type already defined, so you can load directly the node using functions of the pathauto internal API:

pathauto_entity_insert($node_article);

2-Without pathauto:

use Drupal\path_alias\Entity\PathAlias;

$path_alias = PathAlias::create([
  'path' => '/node/' . $node_article->id(),
  'alias' => '/newsletter/kplan',
  ]);
$path_alias->save();

Adding a Paragraph

Maybe you have some paragraphs available in your Drupal installation. You can load some paragraph in your content type and fill the values by code.

In this case we’re using a simple paragraph with a text field that allows full HTML.

First: Creating a text for the paragraph.

$text = '<h2 class="h2-my-paragraph">See the last info in this paragraph.</h2>';

Second: Creating a new paragraph using the type already created in your Drupal installation.

use Drupal\paragraphs\Entity\Paragraph;
...
  $paragraph = Paragraph::create([
    'type' => 'paragraph_custom',   
    'pc_text' => [  
      'value' => $text,                  
      'format' => 'full_html', 
    ],
  ]);
  $paragraph->save();

Third: Add the paragraph element to the main node.

use Drupal\file\Entity\File;
use Drupal\node\Entity\Node;
use Drupal\path_alias\Entity\PathAlias;
use Drupal\taxonomy\Entity\Term;
use Drupal\paragraphs\Entity\Paragraph;
...

$node_article = Node::create([
    'type' => 'article',
    'langcode' => 'en',
    'created' => $requested_time,
    'changed' => $requested_time,
    'uid' => 1,
    'title' => 'Article number one',
    'field_tags' => $terms[1]->tid,
    'body' => [
      'summary' => 'Summary for the node created programmatically.',
      'value' => "This is the body of the node <br> allows HTML tags if needed.",
      'format' => 'full_html',
    ],
    'field_image' => [
      [
        'target_id' => $file->id(),
        'alt' => 'Alt text for the image',
        'title' => 'Title for the image',
      ],
    ],
    'field_paragraph' => [
      [
        'target_id' => $paragraph->id(),
        'target_revision_id' => $paragraph->getRevisionId(),
       ],
     ],
  ]);

  // Save the node.
  $node_article->save();

Adding fields for the Content Type

If you need add more fields to the content type by code, you can use the next code and put it in a .install file or in a hook_update_N().

The next snippet will add a basic text field to the Content Type “Article”, creating its own tables (node__field_, node_revision__field_) in database:

use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;

$field_storage = FieldStorageConfig::create([
    'field_name' => 'field_calasparra', // Using machine_name
    'entity_type' => 'node',
    'type' => 'text',
  ]);
  $field_storage->save();

  $field = FieldConfig::create([
    'field_name' => 'field_calasparra',
    'entity_type' => 'node',
    'bundle' => 'article',
    'label' => 'Testing field creation by code', // Extended name.
  ]);
  $field->save();

You can see this custom module example in my Gitlab repository here, and download with more resources from the main repository in Gitlab.

Extra: Remember that you can create nodes for testing using Drupal Console -for example- or Drush using the Devel Generate module from the Devel family. Creating nodes in Drupal 8 with Drupal Console (Module Devel Generate style):

drupal create:nodes article --limit="6" --title-words="2" --time-range="10" --language="en"
# It will ask about add revisions by prompt

:wq!


Written on May 17, 2020