ChatGPT and Drupal: The crucial role of developer expertise

I recently had to create a custom Views filter for Drupal 10. I quickly wrote the initial version of the plugin, but I knew I had to refactor it to use dependency injection to make it easier to maintain and test in the future. This seemed like a great chance to try out ChatGPT 4. I hoped it could help me refactor my code faster. But as I found out, while AI can be a huge help, you can't just use it blindly. Here's what happened.

My initial prompt to the ChatGPT was:

Rewrite the following custom Drupal 10 Views filter to use dependency injection:

followed by the custom Views filter code that I wanted to refactor:

<?php

namespace Drupal\MY_MODULE\Plugin\views\filter;

use Drupal\MY_MODULE\ClientInterface;
use Drupal\user\UserInterface;
use Drupal\views\Plugin\views\filter\FilterPluginBase;

/**
 * Provides a custom filter for filtering by currency.
 *
 * @ingroup views_filter_handlers
 * @ViewsFilter("currency_filter")
 */
class CurrencyFilter extends FilterPluginBase {

  /**
   * {@inheritdoc}
   */
  public function query() {
    $this->ensureMyTable();

    $current_user_id = \Drupal::currentUser()->id();
    $current_user = \Drupal::entityTypeManager()
      ->getStorage('user')
      ->load($current_user_id);

    if (
      $current_user instanceof UserInterface &&
      $current_user->get('field_client')->isEmpty() === FALSE
    ) {
      $client = $current_user->get('field_client')->entity;
      if (
        $client instanceof ClientInterface &&
        $client->get('price_list_currency')->isEmpty() === FALSE
      ) {
        $currency = $client->get('price_list_currency')->value;

        /** @var \Drupal\views\Plugin\views\query\Sql $query */
        $query = $this->query;
        $query->addWhere($this->options['group'], 'commerce_product_variation_field_data_commerce_product__variations.price__currency_code', [$currency], 'IN');
      }
    }
  }

}

After I submitted my prompt, the gears of ChatGPT's AI kicked into motion, processing my request. With an impressive display of machine learning, the model quickly analyzed the original code and provided a refactored version using dependency injection. It seemed to have seamlessly accomplished the task at hand:

<?php

namespace Drupal\MY_MODULE\Plugin\views\filter;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\MY_MODULE\ClientInterface;
use Drupal\user\UserInterface;
use Drupal\views\Plugin\views\filter\FilterPluginBase;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Provides a custom filter for filtering by currency.
 *
 * @ingroup views_filter_handlers
 * @ViewsFilter("currency_filter")
 */
class CurrencyFilter extends FilterPluginBase implements ContainerFactoryPluginInterface {

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Constructs a new CurrencyFilter object.
   *
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) {
    $this->currentUser = $current_user;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $container->get('current_user'),
      $container->get('entity_type.manager')
    );
  }

  /**
   * {@inheritdoc}
   */
  public function query() {
    $this->ensureMyTable();

    $current_user_id = $this->currentUser->id();
    $current_user = $this->entityTypeManager
      ->getStorage('user')
      ->load($current_user_id);

    if (
      $current_user instanceof UserInterface &&
      $current_user->get('field_client')->isEmpty() === FALSE
    ) {
      $client = $current_user->get('field_client')->entity;
      if (
        $client instanceof ClientInterface &&
        $client->get('price_list_currency')->isEmpty() === FALSE
      ) {
        $currency = $client->get('price_list_currency')->value;

        /** @var \Drupal\views\Plugin\views\query\Sql $query */
        $query = $this->query;
        $query->addWhere($this->options['group'], 'commerce_product_variation_field_data_commerce_product__variations.price__currency_code', [$currency], 'IN');
      }
    }
  }

}

While the refactored code provided by ChatGPT 4 appeared correct at first glance, the reality came to light when I moved past the initial inspection. I copied and pasted the generated code into my Drupal 10 module and proceeded to test the functionality. To my surprise, I was presented with the following warnings:

Warning: Undefined array key "title" in Drupal\views\Plugin\views\HandlerBase->adminLabel() (line 165 of /var/www/html/public_html/core/modules/views/src/Plugin/views/HandlerBase.php)
Warning: Undefined array key "group" in Drupal\views\Plugin\views\HandlerBase->adminLabel() (line 166 of /var/www/html/public_html/core/modules/views/src/Plugin/views/HandlerBase.php)

On top of that, the problem wasn't just the warnings. When I checked out the filter plugin in the Views UI, it wasn't looking right. It ended up looking like a weird smiley face, which you can see in the screenshot below.

Image

Upon examining the refactored code more closely, I realized that the constructor in the provided code didn't call the parent plugin's constructor. In Drupal, this is essential for initializing some of the parent class properties correctly. The omission of this key step led to the malfunctioning of the plugin, hence the error I encountered.

After spotting the issues with the code, I pointed out to ChatGPT that the parent plugin constructor wasn't being called, which meant the plugin wasn't being initialized properly. Recognizing the issue, ChatGPT promptly corrected its previous output. It provided the revised code that included the missing call to the parent constructor in the class constructor.

Updated create() and __construct() methods:

/**
   * Constructs a new CurrencyFilter object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param \Drupal\Core\Session\AccountInterface $current_user
   *   The current user.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, AccountInterface $current_user, EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->currentUser = $current_user;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration, $plugin_id, $plugin_definition,
      $container->get('current_user'),
      $container->get('entity_type.manager')
    );
  }

And that's a wrap! This journey not only underscores the potential of AI and ChatGPT in streamlining Drupal development but also the importance of human expertise. While AI can assist us, knowing what we're doing is crucial.

So, remember, as you harness AI's power, keep your knowledge and skills in the driver's seat to spot and correct any potential AI-generated missteps. Happy coding!

About the Author

Goran Nikolovski is an experienced web and AI developer skilled in Drupal, React, and React Native. He founded this website and enjoys sharing his knowledge.