Skip to main content

This questionnaire is designed to assess knowledge, experience, and expertise in Drupal development. It covers various aspects, including core concepts, module development, theming, site building, and best practices. The responses will help evaluate a candidate's or team's proficiency in working with Drupal projects.

FAQ Component
Question
Could you describe your experience with different versions of Drupal, specifically Drupal 7 to 11? What are the main differences you’ve encountered ?
Answer

I have extensive experience working with different versions of Drupal, primarily from Drupal 7 to Drupal 10, and I am currently preparing for Drupal 11. Here’s a breakdown of my experience and the key differences I’ve encountered:

Drupal 7

  • Hook-based system: Relied heavily on procedural hooks for almost everything.
  • Limited Object-Oriented Programming (OOP): Most of the development was procedural PHP.
  • No Configuration Management (CMI): Configuration was stored in the database, making deployment difficult.
  • Fieldable Entities: Introduced entity and field API but was less flexible than later versions.
  • Twig not available: Used PHP template files for theming.

Drupal 8

  • OOP and Symfony Integration: Introduced Symfony components, making Drupal more modern and maintainable.
  • Configuration Management (CMI): Allowed developers to store configuration in YAML files.
  • Twig Template Engine: Replaced PHP templates for more secure and flexible theming.
  • Plugin System: Replaced many hooks with plugins (e.g., blocks, field types, etc.).
  • REST API Support: Built-in REST services for headless applications.
  • Migration System: Introduced a structured migration system for upgrading from Drupal 7.

Drupal 9

  • Cleaned up deprecated code: Removed deprecated APIs from Drupal 8.
  • Symfony 4.4 / 5.x Upgrade: Ensured better performance and security.
  • Backward Compatibility: Made upgrading from Drupal 8 seamless.
  • More Composer-Based Workflow: Fully embraced Composer for dependency management.

Drupal 10

  • Symfony 6 / PHP 8.1: Improved performance, security, and support for modern PHP.
  • Claro Admin Theme: Introduced as the new default admin theme.
  • Olivero Frontend Theme: A modern and accessible theme.
  • CKEditor 5: Replaced CKEditor 4 for better editing experience.
  • jQuery Deprecation: Reduced reliance on jQuery in favor of modern JavaScript.

Drupal 11 (Upcoming)

  • Further Symfony and PHP updates: Likely to support Symfony 7 and PHP 8.2+.
  • CKEditor 5 Enhancements: More integrations and features.
  • Module and Theme Modernization: Expecting continued improvements in APIs and theming.

Key Differences & Challenges

  1. Migration from Drupal 7: Requires a full content migration due to architectural changes.
  2. OOP vs. Hooks: Transitioning from procedural hooks in Drupal 7 to an object-oriented approach in Drupal 8+.
  3. Composer Dependency Management: Essential for Drupal 8+, making module and library management easier but requiring adaptation.
  4. Theming with Twig: More structured but a shift from direct PHP-based theming in Drupal 7.
  5. Configuration Synchronization: A game-changer from Drupal 8 onwards, making site deployment much smoother.
Question
Can you give an example of a custom module you've built? What challenges did you face, and how did you overcome them?
Answer

I developed a custom module for the Raise Request feature in Drupal 9 for the IIFL Finance project. The module interacts with the getAllProspectApi, applies business logic to validate requests, and ensures that tickets cannot be generated within 30 days from the disbursement date for certain subcategories.


Key Features Implemented

  1. Custom Form for Ticket Creation

    • A user-friendly form for submitting requests.
    • Prepopulated fields using API responses.
  2. API Integration with getAllProspectApi

    • Fetches prospect details dynamically.
    • Filters responses based on Prospect_no and Branch_Code.
  3. Business Rule Enforcement

    • Implements a condition to prevent ticket generation for specific subcategories within 30 days of the disbursement date.
  4. Validation & Error Handling

    • Custom validation for alphanumeric Prospect_no and Branch_Code.
    • User-friendly error messages.
  5. Drupal Messenger for Notifications

    • Displays success/error messages to users.

Challenges & Solutions

1. API Response Handling & Performance Issues

  • Challenge: The API returned a large dataset, causing performance lags.
  • Solution: Implemented batch processing and pagination to optimize API calls and reduce load time.

2. Ensuring Business Rule Enforcement

  • Challenge: Validating 30-day restrictions dynamically based on API data.
  • Solution: Converted the disbursement_date from API response to a timestamp and checked it against the current date before allowing submission.

3. Drupal Form Validation Issues

  • Challenge: The validation for Prospect_no and Branch_Code needed to be robust against user input errors.
  • Solution: Used #element_validate and regex patterns to ensure only alphanumeric values were accepted.

4. Managing API Failures & Error Handling

  • Challenge: If the API was down, the form had to handle failures gracefully.
  • Solution: Implemented try-catch blocks, displayed fallback messages, and logged errors for debugging.
Question
Tell me about your experience with Drupal upgrades, especially moving sites from Drupal 9 to10. What challenges have you faced, and how did you resolve them?
Answer

I have worked extensively on Drupal upgrades, including moving sites from Drupal 8 to 9 and Drupal 9 to 10. Most recently, I have been preparing for the Drupal 10 upgrade for the IIFL Finance project.


Key Steps in the Upgrade Process

  1. Assessing Compatibility with upgrade_status

    • Used the Upgrade Status module to scan for deprecated code and incompatible modules.
    • Identified contrib and custom modules that needed updates.
  2. Updating Contributed Modules & Themes

    • Checked module compatibility via Drupal.org.
    • Updated dependencies using Composer (composer update).
    • Replaced deprecated modules like CKEditor 4 with CKEditor 5.
  3. Custom Code Refactoring

    • Removed deprecated functions (drupal_set_message(), drupal_render(), etc.).
    • Updated service definitions to align with Symfony 6.
    • Replaced deprecated jQuery functions with modern JavaScript.
  4. Database Schema & Configuration Updates

    • Ran drush updb for database updates.
    • Exported and imported configurations (drush cex && drush cim).
  5. Testing & Debugging

    • Conducted functional testing to verify form submissions, API calls, and UI interactions.
    • Used PHPStan and Drupal Rector to scan for deprecated code.
    • Debugged issues using devel module and Drupal logs.

Challenges & Solutions

1. Deprecated Code in Custom Modules

  • Challenge: Some custom modules had deprecated functions that caused errors.
  • Solution: Used Drupal Rector to automate code refactoring and manually replaced unsupported functions.

2. CKEditor 5 Migration Issues

  • Challenge: CKEditor 5 required new configurations and plugins.
  • Solution: Reconfigured text formats and enabled necessary plugins (Link, Image, Table, etc.).

3. Contrib Module Incompatibility

  • Challenge: Some contrib modules were not yet ready for Drupal 10.
  • Solution:
    • Checked Drupal.org for patches.
    • Used composer prohibit to block incompatible modules.
    • Replaced unsupported modules with alternatives.

4. Symfony 6 and PHP 8.1 Compatibility

  • Challenge: Some functions and services had breaking changes due to Symfony updates.
  • Solution:
    • Updated custom service definitions to use dependency injection properly.
    • Used composer why-not drupal/core to check compatibility issues.

5. Twig and Theming Issues

  • Challenge: Some template files (.twig) used deprecated syntax.
  • Solution: Updated templates based on Drupal 10 theming guidelines.
Question
When would you use an event subscriber versus a hook in Drupal? Can you share examples where event subscribers offered a better solution than hooks?
Answer

n Drupal, both hooks and event subscribers allow developers to modify and extend functionality, but they differ in approach and use cases. Here’s when you would choose one over the other:

When to Use Hooks

  • Modify core/module behavior quickly: Hooks are great for overriding or altering existing functionality (e.g., hook_form_alter() to modify a form).
  • Procedural simplicity: If you need a simple modification without requiring additional service definitions, a hook is more straightforward.
  • Execution order is not critical: Hooks execute in a predefined sequence but may not allow precise control over execution order.

When to Use Event Subscribers

  • More structured and reusable code: Since event subscribers use object-oriented programming, they are better for maintainability.
  • Need for execution control: Event subscribers allow priority levels to be set, letting you control when they execute relative to other listeners.
  • Reacting to Symfony events: If you need to work with Drupal’s event-driven architecture (like kernel requests, responses, or entity CRUD operations), event subscribers are required.
  • When multiple modules listen to the same event: Event subscribers let multiple modules react to the same event in a controlled way.

Examples Where Event Subscribers Are Better Than Hooks

1. Modifying Responses for Specific Routes (Kernel Events)

If you need to modify responses before they are sent to the user, using an event subscriber is ideal because it listens to KernelEvents::RESPONSE.

Example: Altering JSON Responses
<?php
namespace Drupal\custom_module\EventSubscriber;
 
use Symfony\Component\EventDispatcher\EventSubscriberInterface; 
use Symfony\Component\HttpKernel\Event\ResponseEvent; 
use Symfony\Component\HttpKernel\KernelEvents; 

class CustomResponseSubscriber implements EventSubscriberInterface {  

public static function getSubscribedEvents() {    
	return [KernelEvents::RESPONSE => ['modifyResponse', 100]];  
}  

public function modifyResponse(ResponseEvent $event) {    
	$response = $event->getResponse();    
	if ($response->headers->get('Content-Type') === 'application/json') { 
	     $data = json_decode($response->getContent(), TRUE);      
	     $data['extra'] = 'Added by event subscriber';      
	     $response->setContent(json_encode($data));    
	   }  
	} 
}
  • Why not use a hook? There’s no hook equivalent for modifying responses at the Symfony level.

2. Handling Entity CRUD Events (e.g., User Login, Node Creation)

Event subscribers are useful for executing custom logic when entities are created, updated, or deleted.

Example: Logging Every New Node Creation
<?php
namespace Drupal\custom_module\EventSubscriber;

use Drupal\node\NodeInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\EventDispatcher\Event;
class NodeCreateSubscriber implements EventSubscriberInterface
{
    protected $logger;
    public function __construct(LoggerChannelFactoryInterface $loggerFactory)
    {
        $this->logger = $loggerFactory->get("custom_module");
    }
    public static function getSubscribedEvents()
    {
        return ["entity.insert" => "onNodeCreate"];
    }
    public function onNodeCreate(Event $event)
    {
        $entity = $event->getSubject();
        if ($entity instanceof NodeInterface) {
            $this->logger->info(
                'A new node with title "@title" has been created.',
                ["@title" => $entity->label()]
            );
        }
    }
}
  • Why not use hook_ENTITY_TYPE_insert()?
    • Hooks only apply per entity type (hook_node_insert(), hook_user_insert()), whereas event subscribers can handle multiple entity types dynamically.

3. Running Custom Logic Before Sending an Email (Mail System)

If you need to intercept and modify emails before they are sent, event subscribers provide a structured approach.

Example: Adding a Custom Footer to Emails
<?php
namespace Drupal\custom_module\EventSubscriber;
use Drupal\Core\Mail\MailEvents;
use Drupal\Core\Mail\MailSendEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class CustomMailSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [MailEvents::MAIL_SEND => ["modifyMailBody"]];
    }
    public function modifyMailBody(MailSendEvent $event)
    {
        $message = $event->getMessage();
        $message["body"][] =
            "\n\n---\nThis email was auto-generated by Custom Module.";
        $event->setMessage($message);
    }
}
  • Why not use hook_mail_alter()?
    • hook_mail_alter() runs before the mail is sent but doesn’t provide as much flexibility in execution control.

Conclusion: When to Choose What?

ScenarioUse a HookUse an Event Subscriber
Modifying forms (e.g., adding fields, validation)hook_form_alter()🚫
Altering entities when they are created, updated, or deletedhook_ENTITY_TYPE_insert()✅ More flexible for multiple entities
Modifying HTTP responses🚫KernelEvents::RESPONSE
Logging user logins or node creationhook_user_login()✅ More structured, OOP approach
Sending or modifying emailshook_mail_alter()✅ More control with MailSendEvent

In short: Use hooks for simple modifications where procedural logic is sufficient. Use event subscribers for structured, reusable, and event-driven logic.

Question
Drupal's plugin system is powerful for extending functionality. Could you give examples of custom plugins you've developed, and how you structured them to handle specific use cases?
Answer

Drupal’s plugin system is a flexible way to extend functionality without modifying core or contrib modules. It provides a structured, reusable approach to implementing features.

Examples of Custom Plugins I’ve Developed

1. Custom Field Formatter Plugin

Use case: Formatting a field differently based on business logic.

💡 Example: In an Auction system, we needed to display the bid_amount field differently for active and expired auctions.

Implementation:
  1. Define the Plugin (src/Plugin/Field/FieldFormatter/BidAmountFormatter.php)
namespace Drupal\custom_module\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Form\FormStateInterface;

/**
 * @FieldFormatter(
 *   id = "bid_amount_formatter",
 *   label = @Translation("Bid Amount Formatter"),
 *   field_types = {
 *     "decimal"
 *   }
 * )
 */
class BidAmountFormatter extends FormatterBase {
  
  public function viewElements(FieldItemListInterface $items, $langcode) {
    $elements = [];

    foreach ($items as $delta => $item) {
      $bid_amount = $item->value;
      $status = $items->getEntity()->get('status')->value;

      $formatted_value = $status === 'active' 
        ? "<strong>$bid_amount</strong> (Live Auction)"
        : "<span style='color:gray;'>$bid_amount</span> (Expired)";

      $elements[$delta] = ['#markup' => $formatted_value];
    }

    return $elements;
  }
}

Why a plugin?

  • Using a Field Formatter Plugin allows us to format the field dynamically instead of altering the template.

2. Custom Block Plugin

Use case: Displaying dynamic loan eligibility calculations in a Banking/Finance website.

💡 Example: A Loan Calculator Block that users can place anywhere, fetching real-time interest rates.

Implementation:
  1. Define the Plugin (src/Plugin/Block/LoanCalculatorBlock.php)
namespace Drupal\custom_module\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * @Block(
 *   id = "loan_calculator_block",
 *   admin_label = @Translation("Loan Calculator Block")
 * )
 */
class LoanCalculatorBlock extends BlockBase {
  
  public function build() {
    return [
      '#markup' => $this->generateCalculatorForm(),
      '#cache' => ['max-age' => 0], // Disable cache for dynamic updates.
    ];
  }

  private function generateCalculatorForm() {
    return '<form>
              <label>Loan Amount:</label>
              <input type="text" name="loan_amount">
              <button type="submit">Calculate</button>
            </form>';
  }
}



Why a plugin?

  • Reusable: The block can be placed on any page.
  • Dynamic logic: Can be extended to use AJAX for real-time calculations.

 

3. Custom Queue Worker Plugin

Use case: Handling large-scale CSV imports asynchronously in the IIFL Finance project.

💡 Example: Processing auction properties CSV using a queue system.

Implementation:
  1. Define the Plugin (src/Plugin/QueueWorker/AuctionPropertyProcessor.php)
namespace Drupal\custom_module\Plugin\QueueWorker;

use Drupal\Core\Queue\QueueWorkerBase;

/**
 * @QueueWorker(
 *   id = "auction_property_processor",
 *   title = @Translation("Auction Property Processor"),
 *   cron = {"time" = 60}
 * )
 */
class AuctionPropertyProcessor extends QueueWorkerBase {
  
  public function processItem($data) {
    \Drupal::logger('custom_module')->notice('Processing auction property: @property', [
      '@property' => $data['property_name']
    ]);

    // Process auction property data
    // Example: Save it as a node or update existing data.
  }
}

Why a plugin?

  • Asynchronous processing: Large CSVs don’t block user actions.
  • Performance: Helps optimize performance by breaking the processing into batches.

4. Custom Migrate Source Plugin

Use case: Custom data migration from a third-party API to Drupal.

💡 Example: Migrating loan application data from an external API into Drupal nodes.

Implementation:
  1. Define the Plugin (src/Plugin/migrate/source/LoanApplication.php)
namespace Drupal\custom_module\Plugin\migrate\source;

use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
use GuzzleHttp\Client;

/**
 * @MigrateSource(
 *   id = "loan_application"
 * )
 */
class LoanApplication extends SourcePluginBase {
  
  public function initializeIterator() {
    $client = new Client();
    $response = $client->get('https://api.example.com/loan-applications');
    $data = json_decode($response->getBody(), TRUE);
    return new \ArrayIterator($data);
  }
}

Why a plugin?

  • Custom API integration: Fetches external data instead of relying on a static CSV.
  • Extensible: Can be modified for different data structures.

Conclusion: When to Use a Plugin in Drupal?

Use CaseType of PluginBenefit
Customizing how a field is displayedField FormatterClean, reusable, and configurable UI formatting
Creating a reusable site blockBlock PluginEasily configurable and placed anywhere
Processing background tasks (e.g., CSV imports)Queue Worker PluginAsynchronous execution for better performance
Migrating external data (e.g., API or database)Migrate Source PluginFetches and processes data dynamically
Question
Can you explain how dependency injection works in Drupal 8 and 9? How have you used it in custom modules, and what advantages does it provide over global function calls?
Answer

Understanding Dependency Injection in Drupal 8 & 9

Dependency Injection (DI) is a design pattern where objects receive their dependencies (services) instead of creating them manually. In Drupal 8 and 9, DI is widely used to promote better code reusability, testability, and maintainability by injecting services rather than using global calls like \Drupal::service().

Why Use Dependency Injection?

FeatureGlobal Calls (\Drupal::service())Dependency Injection
TestabilityHard to mock services for unit testsEasily mock services
PerformanceMultiple calls can create redundant instancesInjected once, improving efficiency
MaintainabilityTight coupling with global stateLoose coupling, making code reusable
Code ClarityHard to trace dependenciesClearly defined dependencies in constructors

How to Use Dependency Injection in Custom Modules?

1. Using DI in a Custom Service

💡 Example Use Case: Logging auction property updates.

Step 1: Define the Service (custom_module.services.yml)

services:
  custom_module.logger_service:
    class: Drupal\custom_module\Service\LoggerService
    arguments: ['@logger.factory']

Step 2: Create the Service Class (src/Service/LoggerService.php)

namespace Drupal\custom_module\Service;

use Drupal\Core\Logger\LoggerChannelFactoryInterface;

class LoggerService {
  protected $logger;

  public function __construct(LoggerChannelFactoryInterface $loggerFactory) {
    $this->logger = $loggerFactory->get('custom_module');
  }

  public function logMessage($message) {
    $this->logger->info($message);
  }
}

Step 3: Inject the Service into a Controller (src/Controller/CustomController.php)

namespace Drupal\custom_module\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\custom_module\Service\LoggerService;

class CustomController extends ControllerBase {
  protected $loggerService;

  public function __construct(LoggerService $loggerService) {
    $this->loggerService = $loggerService;
  }

  public static function create(ContainerInterface $container) {
    return new static($container->get('custom_module.logger_service'));
  }

  public function logAuctionUpdate() {
    $this->loggerService->logMessage('Auction property updated.');
    return ['#markup' => 'Log entry added.'];
  }
}

Advantages of DI here:

  • The LoggerService is injected instead of using \Drupal::logger().
  • This makes it mockable for testing and loosely coupled.

 

2. Injecting Services in Plugins (Example: Block Plugin)

💡 Example Use Case: Fetching dynamic interest rates from a configuration service.

Step 1: Inject the Config Factory in a Block Plugin

namespace Drupal\custom_module\Plugin\Block;

use Drupal\Core\Block\BlockBase;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * @Block(
 *   id = "loan_interest_rate_block",
 *   admin_label = @Translation("Loan Interest Rate Block")
 * )
 */
class LoanInterestRateBlock extends BlockBase {
  protected $configFactory;

  public function __construct(array $configuration, $plugin_id, $plugin_definition, ConfigFactoryInterface $configFactory) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->configFactory = $configFactory;
  }

  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container->get('config.factory'));
  }

  public function build() {
    $interest_rate = $this->configFactory->get('custom_module.settings')->get('interest_rate');
    return ['#markup' => "Current Loan Interest Rate: {$interest_rate}%"];
  }
}

Advantages of DI here:

  • No \Drupal::config() global call, making the block independent of the global state.
  • Easier unit testing by mocking the config.factory service.

 

3. Injecting Services in an Event Subscriber

💡 Example Use Case: Logging a message when a user logs in.

Step 1: Define the Event Subscriber (src/EventSubscriber/UserLoginSubscriber.php)

namespace Drupal\custom_module\EventSubscriber;

use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\RequestEvent;

class UserLoginSubscriber implements EventSubscriberInterface {
  protected $logger;

  public function __construct(LoggerChannelFactoryInterface $loggerFactory) {
    $this->logger = $loggerFactory->get('user_activity');
  }

  public static function getSubscribedEvents() {
    return [KernelEvents::REQUEST => ['logUserLogin', 0]];
  }

  public function logUserLogin(RequestEvent $event) {
    $this->logger->info('User has logged in.');
  }
}

Advantages of DI here:

  • The logger is injected, making it easy to test.
  • No global calls, keeping the subscriber clean and reusable.

 

Key Takeaways

🔹 Dependency Injection helps in:

  1. Testability: You can easily mock services in unit tests.
  2. Performance: Services are loaded only when needed, avoiding redundant calls.
  3. Maintainability: Code is more structured and reusable.
  4. Encapsulation: Dependencies are clearly defined in constructors.

🔹 Use Dependency Injection when:
✅ You need a reusable service that can be injected anywhere.
✅ You're working with plugins, blocks, controllers, or event subscribers.
✅ You want to avoid global function calls (\Drupal::service()), making your code cleaner.

 

Tag
Drupal