State of AI Coding Assistants for Drupal Web Development

Table of contents

Coding assistants are not a new concept. Code completion aid tools, Microsoft IntelliSense, have been present in many integrated development environments (IDEs) since the late 1990s. These tools offer suggestions in pop-up dialogs as developers, aiding them as they write and helping them by providing options for functions, methods, and variables.
Comment

However, a more recent development in coding assistance involves the integration of artificial intelligence. Instead of just offering single word suggestions, code completion tools that utilize AI can generate full lines or even multiple lines of code based on the surrounding context. GitHub Copilot, introduced in June 2021, is the main example of this type of tool. While not the first AI code assistant, Copilot gained widespread attention due to its backing by Microsoft-owned GitHub, making it widely available to developers.

My first experience with an AI coding assistant was during the open technical preview of GitHub Copilot from late 2021 until its conclusion in August 2022. At that time, I found it better than traditional code completion, but not particularly useful for Drupal development. Drupal related suggestions were often incorrect or required too much manual fixing. For instance, most array suggestions used the outdated ‘array()’ syntax instead of the modern square brackets.

Nevertheless, I saw the potential of AI coding completion tools as it performed notably better with languages like React.js or Python compared to PHP and Drupal. In March 2023, after GitHub announced the addition of a GPT-based chat feature in Copilot, I made GitHub Copilot an integral part of my development toolkit. I also explored different alternatives such as TabNine, Amazon CodeWhisperer, and more recently Codeium, all offering similar features and results.

The past couple of weeks I have been testing what AI Coding Assistants can do for Drupal backend development. In this article I share some of my findings.

AI coding assistant for Drupal developers

Most coding assistants provide a chat function where you can enter prompts to generate something. This can be a good replacement for scaffolding tools like Drush generate or Drupal console. The advantage of an AI Coding assistant is that it can not only provide the scaffolding, but if you explain what you want to achieve it can also provide a more tailored solution.

Lets see a practical example. Suppose we have a custom “Dataset” module that offers a dashboard accessible via the route dataset.dashboard. Now we want to create a custom dynamic breadcrumb that displays the name of the current user as part of the breadcrumb text.

If you know how to do this in Drupal, you can create a detailed prompt like the one below:

Create a Drupal 10 breadcrumb builder service for route dataset.dashboard that shows the “[account names]’s Dashboard” inside a module called dataset

Depending on the tool you are using, you will get an answer that will perfectly solve the problem or a solution that will require some tweaks.

With the Codeium base model, which is available for free for individuals, I obtained the following code for the service class:

services:
  dataset.breadcrumb_builder:
    class: Drupal\dataset\DatasetBreadcrumbBuilder
    tags:
      - { name: breadcrumb_builder }
<?php

namespace Drupal\dataset;

use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Link;
use Drupal\Core\Url;

class DatasetBreadcrumbBuilder implements BreadcrumbBuilderInterface {

  public function applies(RouteMatchInterface $route_match) {
    return $route_match->getRouteName() === 'dataset.dashboard';
  }

  public function build(RouteMatchInterface $route_match) {
    $breadcrumb = new \Drupal\Core\Breadcrumb\Breadcrumb();
    $breadcrumb->addLink(Link::createFromRoute(t('Home'), '<front>'));

    // You can replace '[account names]' dynamically based on your logic
    $breadcrumb->addLink(Link::createFromRoute('Account Name\'s Dashboard', 'dataset.dashboard'));

    return $breadcrumb;
  }

}
 Comment
Using Codeium's GPT-4 based model, which is available on their Teams plan, I received the following code instead:

services:
  dataset.breadcrumb_builder:
    class: Drupal\dataset\Breadcrumb\DatasetBreadcrumbBuilder
    arguments: ['@current_user']
    tags:
      - { name: breadcrumb_builder, priority: 100 }
<?php

namespace Drupal\dataset\Breadcrumb;

use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;

class DatasetBreadcrumbBuilder implements BreadcrumbBuilderInterface {
  use StringTranslationTrait;

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

  /**
   * Constructs the DatasetBreadcrumbBuilder.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
   *   The current user.
   */
  public function __construct(AccountProxyInterface $currentUser) {
    $this->currentUser = $currentUser;
  }

  /**
   * {@inheritdoc}
   */
  public function applies(RouteMatchInterface $route_match) {
    // Only apply this breadcrumb builder for the 'dataset.dashboard' route.
    return $route_match->getRouteName() === 'dataset.dashboard';
  }

  /**
   * {@inheritdoc}
   */
  public function build(RouteMatchInterface $route_match) {
    $breadcrumb = new Breadcrumb();

    // Add cache contexts so that the breadcrumb can be cached accurately.
    $breadcrumb->addCacheContexts(['route', 'user']);

    // Add the 'Home' link.
    $breadcrumb->addLink(Link::createFromRoute($this->t('Home'), '<front>'));

    // Retrieve the current user's display name.
    $account_name = $this->currentUser->getAccount()->getDisplayName();

    // Add the "[account name]'s Dashboard" link.
    $breadcrumb->addLink(new Link($this->t("@name's Dashboard", ['@name' => $account_name]), $route_match->getUrlObject()));

    return $breadcrumb;
  }
}

Both are good solutions, but the GPT-4 model correctly applied dependency injection for the current user service, and referenced classes using use statements. Both solutions fall short in following Drupal coding standards for class doc comment but the solution from the GPT-4 model is really good.

Comparing to Code generators

Is using an AI code assistant any better than using a code generator such as Drush generate? Well, let’s see what we can get from it. To get a breadcrumb builder class, we have to use the following command.

drush generate service:breadcrumb-builder 

We will be prompted to enter the machine name of the module where we want the new service class to be created, the class name, and any dependencies we want to inject into our new class. This is the code generated:

services:
 ai_tools.breadcrumb:
   class: Drupal\ai_tools\AiToolsBreadcrumbBuilder
   arguments: ['@current_user']
   tags:
     # In order to override breadcrumbs built with PathBasedBreadcrumbBuilder
     # set the priority higher than zero.
     - { name: breadcrumb_builder, priority: 1000 }
<?php declare(strict_types = 1);

namespace Drupal\ai_tools;

use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;

/**
* @todo Add description for this breadcrumb builder.
*/
final class AiToolsBreadcrumbBuilder implements BreadcrumbBuilderInterface {

 use StringTranslationTrait;

 /**
  * Constructs an AiToolsBreadcrumbBuilder object.
  */
 public function __construct(
   private readonly AccountProxyInterface $currentUser,
 ) {}

 /**
  * {@inheritdoc}
  */
 public function applies(RouteMatchInterface $route_match): bool {
   return $route_match->getRouteName() === 'example';
 }

 /**
  * {@inheritdoc}
  */
 public function build(RouteMatchInterface $route_match): Breadcrumb {
   $breadcrumb = new Breadcrumb();

   $links[] = Link::createFromRoute($this->t('Home'), '<front>');
   $links[] = Link::createFromRoute($this->t('Example'), '<none>');

   return $breadcrumb->setLinks($links);
 }

}

This simple example illustrates the differences between AI Code assistants and Code generators. The solution provided by Drush generate is a scaffolding, and the developer will need to manually fill some gaps, like the name of the route this breadcrumb applies, and implement the custom functionality to show the current user name. You also have to know exactly which dependencies you need to inject for your solution.
Comment

There is one advantage of code generators over AI code assistants. The code generators produce code that follows the latest API changes and coding standards. You can see that this example is using constructor property promotion for injected services, a feature that is available in PHP 8 and reduces boilerplate code. This is something that is still a work in progress for Drupal Core, but is a good idea to start implementing it on custom modules.

AI coding assistant for a new Drupal developer

Junior or semi-senior developers who are not as experienced or knowledgeable about Drupal may know what they want to achieve but not how to do it. Let’s revisit the same problem as before, but instead of providing a prompt describing the solution, we will only provide a description of the problem.
Comment

“In Drupal 10 I want to have a breadcrumb that said “[account names]’s Dashboard” for the dashboard page”

For this article, I will only share the code generated by the AI Coding Assistant, but it also provides a detailed explanation of the solution and step-by-step instructions on how to implement it.

# In your custom_module/custom_module.services.yml
services:
  custom_module.breadcrumb_builder:
    class: Drupal\custom_module\Breadcrumb\CustomBreadcrumbBuilder
    tags:
      - { name: breadcrumb_builder, priority: 100 }
// In your custom_module/src/Breadcrumb/CustomBreadcrumbBuilder.php

namespace Drupal\custom_module\Breadcrumb;

use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Link;

class CustomBreadcrumbBuilder implements BreadcrumbBuilderInterface {

  public function applies(RouteMatchInterface $route_match) {
    // Apply this breadcrumb builder to specific routes
    // Here you should add a condition to check if the route is the dashboard
    $route_name = $route_match->getRouteName();
    return $route_name === 'dashboard.route_name_here'; // Replace with actual dashboard route name
  }

  public function build(RouteMatchInterface $route_match) {
    $breadcrumb = new Breadcrumb();

    // Assuming you have the user object, otherwise retrieve it from the route or current user
    $user = \Drupal::currentUser();
    $account_name = $user->getDisplayName();

    // Add the Home link (optional)
    $breadcrumb->addLink(Link::createFromRoute(t('Home'), '<front>'));

    // Add the custom Dashboard breadcrumb
    $breadcrumb->addLink(Link::createFromRoute(t("@name's Dashboard", ['@name' => $account_name]), 'dashboard.route_name_here'));

    // Add cache control, so it depends on the route and user context
    $breadcrumb->addCacheContexts(['route', 'user']);

    return $breadcrumb;
  }
}

As you can see, the provided solution is correct but will require a little more manual work from the developer or follow-up with the AI chat to provide missing information, such as the module’s name and the machine name for the dashboard page route.
Comment

For this specific example, the tool set the developer on the right track and likely saved a significant amount of time researching and browsing documentation. However, there is a significant risk: someone who can’t quickly tell whether the solution provided by the tool is correct may end up spending time implementing something that ultimately doesn’t meet the requirements.

When I first used ChatGPT and GitHub Copilot, many of the suggestions were incorrect in some way or another. Many were solutions for Drupal 7, and I even received suggestions that recommended using modules or features that don’t exist. While I haven’t encountered this issue as frequently recently, it still occurs from time to time, and developers should be aware of it and ask for advice to more experienced developers instead of going into rabbit holes.

The chat function is also great as a learning tool. It doesn’t matter if you are a new or an experienced Drupal developer, it is much faster asking a chatbot how to do something than trying to find out the answer by browsing through the Drupal documentation or exploring its code base.

Common issues with AI Coding assistants in Drupal

Suggestions for Drupal 7

I have seen some instances where AI coding assistants may provide suggestions tailored for Drupal 7 instead of Drupal 8+ even when specifying Drupal 10 in the prompt. For example, suggesting the use of hook_menu instead of *.routing.yml. While this occurrence has become less frequent, it still happens occasionally and may require manual clarification in the chat that you use newer version of Drupal.

Deprecated code

One of the biggest issues with the AI coding assistants is the inclusion of deprecated functions or methods in their suggestions. While code with deprecations may still function, it’s not advisable to create new code that triggers deprecation warnings.
Comment

When I started using Copilot almost two years ago, I remember getting code that still used the entity.manager service. Lucky that is not the case anymore which means that they trained their model with a more up to date dataset, in my tests during the past two weeks I still got some suggestions with deprecations like the use of REQUEST_TIME constant instead of the Time service introduced with Drupal 8.3 on October 2016.

Missing use statements

Generated code may sometimes lack necessary use statements for interfaces or classes referenced within it. For example, a logging service class that omited the use statement for the LogMessageParserInterface required by one of its constructor parameters.

Dependency injection

Much of the generated code overlooks proper dependency injection of services, instead suggesting accessing services statically through methods like \Drupal::currentUser() rather than injecting them through class constructors.

Coding standards

The last of my issues with the current coding assistants in Drupal, is that they do not always follow or stay updated with the latest Drupal coding standards. While this may seem minor, the generated code often requires adjustments such as fixing indentation, spacing, adding or correcting doc comments, and alphabetizing use statements to ensure compliance with standards.

Conclusion

Despite their current shortcomings, senior Drupal developers that have a clear understanding of their objectives and can explain them in a prompt, can benefit from an AI coding assistant to generate code. These tools can save time by automating the writing of code, thus reducing manual typing and minimizing the likelihood of typos. Additionally, they eliminate the need to constantly reference documentation for class and method interfaces.
Comment

However, considering the current state of AI technology, it’s important to understand that the generated code will still require revision and likely need correction.

If you are new to drupal backend development, AI coding assistants can be valuable learning tools, helping understand how to accomplish tasks. It’s essential to allocate time for careful review and understanding of the generated code to ensure its correctness and understand the underlying principles. Seeking guidance from a senior developer or referring to Drupal documentation is advisable if anything appears unclear or questionable.

In my testing, I’ve evaluated dozens of prompts covering the most common Drupal APIs, and the resulting solutions have shown a high success rate. AI coding assistants are already very useful, and will continue to improve over time.