Namespace e autoloading in PHP: organizzare interfacce, trait ed enum

by theArchitect
SHARE
Namespace e autoloading in PHP: organizzare interfacce, trait ed enum
© Guida-HTML5.it

Introduzione

Quando un progetto PHP cresce, non vengono aggiunte soltanto classi concrete. Diventano importanti anche interfacce, trait ed enum. Questi elementi permettono di definire contratti, riutilizzare comportamenti e rappresentare valori limitati in modo sicuro.

Un errore comune consiste nel caricare automaticamente solo le classi principali e lasciare interfacce, trait o enum in file inclusi manualmente con require. Questa soluzione può funzionare all’inizio, ma rende il codice fragile e difficile da mantenere.

In questo tutorial costruiremo un piccolo sistema di notifiche. L’esempio mostrerà come organizzare i file con i namespace e come configurare l’autoloading PSR-4 affinché PHP trovi automaticamente tutti i componenti necessari, inclusi interfacce, trait ed enum.

Codice completo

La struttura del progetto sarà la seguente:

notifier/
├── composer.json
├── public/
│   └── index.php
└── src/
    ├── Contract/
    │   └── NotifierInterface.php
    ├── Enum/
    │   └── NotificationChannel.php
    ├── Support/
    │   └── LogsMessages.php
    └── Service/
        └── NotificationService.php

Configuriamo Composer nel file composer.json:

{
    "name": "demo/notifier",
    "description": "Esempio di namespace e autoloading",
    "require": {
        "php": "^8.1"
    },
    "autoload": {
        "psr-4": {
            "DemoNotifier": "src/"
        }
    }
}

L’interfaccia definisce il comportamento che ogni sistema di notifica deve rispettare:

<?php

namespace DemoNotifierContract;

use DemoNotifierEnumNotificationChannel;

interface NotifierInterface
{
    public function send(
        string $recipient,
        string $message,
        NotificationChannel $channel
    ): void;
}

L’enum limita i canali disponibili, evitando stringhe arbitrarie:

<?php

namespace DemoNotifierEnum;

enum NotificationChannel: string
{
    case EMAIL = ´email´;
    case SMS = ´sms´;
    case PUSH = ´push´;
}

Il trait contiene una funzionalità riutilizzabile per registrare i messaggi inviati:

<?php

namespace DemoNotifierSupport;

trait LogsMessages
{
    private array $logs = [];

    protected function logMessage(string $message): void
    {
        $this->logs[] = sprintf(
            ´[%s] %s´,
            date(´Y-m-d H:i:s´),
            $message
        );
    }

    public function getLogs(): array
    {
        return $this->logs;
    }
}

La classe concreta implementa l’interfaccia e utilizza il trait:

<?php

namespace DemoNotifierService;

use DemoNotifierContractNotifierInterface;
use DemoNotifierEnumNotificationChannel;
use DemoNotifierSupportLogsMessages;

final class NotificationService implements NotifierInterface
{
    use LogsMessages;

    public function send(
        string $recipient,
        string $message,
        NotificationChannel $channel
    ): void {
        $log = sprintf(
            ´Invio tramite %s a %s: %s´,
            $channel->value,
            $recipient,
            $message
        );

        $this->logMessage($log);
    }
}

Infine, il punto di ingresso del programma utilizza soltanto l’autoloader di Composer:

<?php

require dirname(__DIR__) . ´/vendor/autoload.php´;

use DemoNotifierEnumNotificationChannel;
use DemoNotifierServiceNotificationService;

$service = new NotificationService();

$service->send(
    ´[email protected]´,
    ´Il tuo ordine è stato spedito´,
    NotificationChannel::EMAIL
);

$service->send(
    ´+391234567890´,
    ´Codice di verifica: 4821´,
    NotificationChannel::SMS
);

foreach ($service->getLogs() as $log) {
    echo $log . PHP_EOL;
}

Per installare e generare l’autoloader esegui:

composer install
composer dump-autoload

Spiegazione

La direttiva:

"DemoNotifier": "src/"

indica che ogni namespace che inizia con DemoNotifier deve essere cercato nella directory src. Per esempio, il namespace:

DemoNotifierEnum

corrisponde alla directory:

src/Enum/

La classe NotificationChannel si trova quindi in src/Enum/NotificationChannel.php. Lo stesso principio vale per interfacce e trait: non esiste una differenza speciale nel meccanismo di autoloading. Ciò che conta è la corrispondenza tra namespace, percorso e nome del file.

Quando PHP incontra il tipo NotificationChannel, Composer verifica la mappa PSR-4 e carica il file corretto. Questo avviene anche quando il tipo viene usato in una dichiarazione di parametro, come in:

public function send(
    string $recipient,
    string $message,
    NotificationChannel $channel
): void

Il costruttore dell’enum viene gestito automaticamente da PHP, mentre il file dell’enum viene caricato dall’autoloader.

La parola chiave use usata dentro una classe per un trait non è la stessa cosa dell’istruzione use usata per importare un namespace. In questo caso:

use LogsMessages;

inserisce i metodi del trait nella classe. Il trait viene comunque risolto tramite il suo namespace e, se necessario, caricato automaticamente.

Best practice

  • Usa un namespace coerente: il prefisso configurato in Composer deve corrispondere alla struttura logica del progetto.
  • Mantieni un elemento principale per file: salva ogni interfaccia, trait, enum e classe in un file dedicato.
  • Preferisci enum ai valori testuali liberi: riducono errori come ´e-mail´, ´email´ e ´Email´.
  • Usa interfacce per definire contratti: una classe dipendente dall’interfaccia è più semplice da sostituire e testare.
  • Limita i trait: sono utili per comportamenti trasversali, ma non dovrebbero diventare contenitori di logica complessa.
  • Rigenera l’autoloader dopo aver modificato la configurazione: esegui composer dump-autoload.
  • Verifica la versione di PHP: gli enum richiedono PHP 8.1 o versioni successive.
  • Non usare require manuali per ogni file: includi una sola volta vendor/autoload.php.
  • Controlla i nomi con precisione: su sistemi case-sensitive, NotificationService.php e notificationservice.php non sono equivalenti.

Riepilogo

Namespace e autoloading non servono soltanto a caricare classi concrete. Lo stesso sistema gestisce in modo uniforme interfacce, trait ed enum, purché la struttura delle cartelle rispetti la configurazione PSR-4.

Nel progetto dell’esempio, l’interfaccia definisce il contratto, l’enum rappresenta i canali validi, il trait riutilizza la logica di registrazione e la classe concreta coordina il comportamento. Grazie all’autoloader di Composer, il file principale non deve conoscere i percorsi fisici dei singoli componenti.

Approfondisci con risorse ufficiali

SHARE