Skip to main content
Home
Drupal life hacks

Main navigation

  • Drupal
  • React
  • WP
  • Contact
  • About
User account menu
  • Log in

Breadcrumb

  1. Home

One Feature, Three Approaches: Drupal Module vs Symfony vs Laravel

By admin, 2 January, 2026
One Feature, Three Approaches: Drupal Module vs Symfony vs Laravel

One Feature, Three Approaches: Drupal Module vs Symfony vs Laravel

Feature Description

We will implement the same simple feature in all three systems:

User Registration Logger

When a new user registers, the system logs:

  • user ID
  • email
  • registration time

Why this feature?

Because it:

  • touches core lifecycle events
  • requires extensibility
  • is implemented without modifying core code
  • exists in real-world projects

Perfect example to compare modularity.


1. Drupal: Module + Event Subscriber

1.1 Module structure

user_register_logger/
├── user_register_logger.info.yml
├── user_register_logger.services.yml
└── src/
    └── EventSubscriber/
        └── UserRegisterSubscriber.php

1.2 Module definition

user_register_logger.info.yml

name: User Register Logger
type: module
description: Logs user registrations
core_version_requirement: ^10 || ^11
package: Custom
dependencies:
  - user

Drupal module is:

  • discoverable
  • enable/disable via UI
  • dependency-aware

1.3 Service registration

user_register_logger.services.yml

services:
  user_register_logger.subscriber:
    class: Drupal\user_register_logger\EventSubscriber\UserRegisterSubscriber
    arguments: ['@logger.channel.user']
    tags:
      - { name: event_subscriber }

1.4 Event Subscriber

namespace Drupal\user_register_logger\EventSubscriber;

use Drupal\user\Event\UserEvents;
use Drupal\user\Event\UserCreateEvent;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class UserRegisterSubscriber implements EventSubscriberInterface {

  public function __construct(
    private LoggerInterface $logger
  ) {}

  public static function getSubscribedEvents(): array {
    return [
      UserEvents::USER_CREATE => 'onUserCreate',
    ];
  }

  public function onUserCreate(UserCreateEvent $event): void {
    $user = $event->getUser();

    $this->logger->info('New user registered: {uid} ({mail})', [
      'uid' => $user->id(),
      'mail' => $user->getEmail(),
    ]);
  }
}

✅ What this shows

  • Feature is a self-contained module
  • No core modification
  • Uses Symfony EventDispatcher
  • Integrated with Drupal logging & permissions

Drupal modularity = feature-level plugins


2. Symfony: Event Listener + Service

Symfony does not have “modules” — only application code + services.


2.1 Event Listener

namespace App\EventListener;

use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Psr\Log\LoggerInterface;

class UserRegisterListener {

  public function __construct(
    private LoggerInterface $logger
  ) {}

  public function onLogin(InteractiveLoginEvent $event): void {
    $user = $event->getAuthenticationToken()->getUser();

    $this->logger->info('User registered/logged in', [
      'user' => $user->getUserIdentifier(),
    ]);
  }
}

2.2 Service configuration

services:
  App\EventListener\UserRegisterListener:
    tags:
      - { name: kernel.event_listener, event: security.interactive_login }

✅ What this shows

  • Feature is code-level, not system-level
  • No runtime enable/disable
  • No UI awareness
  • Modularity achieved via:
    • DI
    • Events
    • Contracts

Symfony modularity = architectural modularity


3. Laravel: Package + Service Provider

Laravel prefers packages for feature isolation.


3.1 Event Listener

namespace App\Listeners;

use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Log;

class LogUserRegistration {

  public function handle(Registered $event): void {
    $user = $event->user;

    Log::info('New user registered', [
      'id' => $user->id,
      'email' => $user->email,
    ]);
  }
}

3.2 Event registration

EventServiceProvider

protected $listen = [
  Registered::class => [
    LogUserRegistration::class,
  ],
];

3.3 Package-style modularity (conceptually)

In real projects this logic usually lives in a Composer package with:

  • ServiceProvider
  • Config
  • Events
  • Commands
class UserRegisterLoggerServiceProvider extends ServiceProvider {
  public function boot() {
    //
  }
}

✅ What this shows

  • Feature modularity via packages
  • Clear lifecycle hooks
  • Strong developer experience
  • Less strict boundaries than Drupal

Laravel modularity = feature packages


4. Direct Comparison

AspectDrupalSymfonyLaravel
Feature isolationModuleServicePackage
Runtime enable/disable✅ Yes❌ No⚠ Composer
Entry point.info.ymlservices.yamlServiceProvider
EventsSymfony-basedNativeNative
DI strictnessHighVery highMedium
CMS integrationFullNoneNone

5. Architectural Insight

The same feature shows three philosophies:

Drupal

“Features must be pluggable by non-developers.”

Symfony

“Architecture must be clean and explicit.”

Laravel

“Developer productivity comes first.”

Drupal sits on top of Symfony, but adds:

  • UI
  • configuration
  • permissions
  • content modeling
  • plugin discovery

Final Takeaway

If you can write this feature cleanly in all three systems, you truly understand modern PHP.

Symfony teaches structure
Laravel teaches flow
Drupal teaches extensibility

Tags

  • Drupal Module Development
  • Drupal modular architecture
  • Symfony modularity
  • Laravel modularity
  • PHP frameworks comparison
  • Drupal vs Symfony vs Laravel
  • Symfony Components
  • Laravel service providers
  • PHP dependency injection
  • event-driven architecture
  • Drupal Plugin API
  • composer packages
  • PHP application architecture

Comments

About text formats

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.
Powered by Drupal