---
title: "Metodi magici in PHP: Sotto il cofano dell'OOP dinamica | DevSense"
description: "Una guida completa per sviluppatori ai metodi magici di PHP. Scopri la promozione delle proprietà del costruttore, l'overloading dinamico di proprietà/metodi, l'evoluzione della serializzazione e i compromessi tra prestazioni e analisi statica."
faq:
    - { question: 'Cosa sono i metodi magici in PHP?', answer: 'I metodi magici in PHP sono speciali metodi predefiniti che iniziano con un doppio carattere di sottolineatura (ad es., __construct, __call, __get) e che PHP chiama automaticamente in risposta a specifici eventi e operazioni sugli oggetti.' }
    - { question: 'Come funziona la promozione delle proprietà del costruttore in PHP 8.0+?', answer: 'Introdotta in PHP 8.0, la promozione delle proprietà del costruttore consente di definire la visibilità public, protected o private direttamente sui parametri del costruttore. PHP crea automaticamente queste proprietà di classe e assegna i valori passati, eliminando il codice boilerplate.' }
    - { question: 'Perché __serialize e __unserialize sono preferiti a __sleep e __wakeup?', answer: 'Introdotti in PHP 7.4, __serialize e __unserialize restituiscono e ripristinano lo stato tramite un array associativo standard. A differenza di __sleep, che restituisce un array di nomi di proprietà, questo approccio è più flessibile, gestisce strutture dati dinamiche ed evita errori di serializzazione per proprietà inesistenti.' }
    - { question: 'I metodi magici di PHP sono lenti?', answer: "Sì, i metodi magici come __get, __set e __call aggiungono un sovraccarico di ricerca a livello di engine. Aggirano i percorsi di proprietà compilati diretti della Zend VM, rendendoli da 3 a 4 volte più lenti rispetto all'accesso diretto a proprietà o metodi. Dovrebbero essere evitati nei percorsi critici per le prestazioni." }
published: '2026-06-07'
---
# Metodi magici in PHP: Sotto il cofano dell'OOP dinamica

Livello target: Middle  
Versione PHP: PHP 7.0+ (Caratteristiche evidenziate con marcatori di versione)

Ogni sviluppatore PHP ha utilizzato `__construct()`, ma pochi si rendono conto che chiamare metodi magici dinamici come `__get()` o `__call()` può rallentare l'accesso alle proprietà fino al 300%, disabilitare l'autocompletamento dell'IDE e causare bug silenziosi che aggirano gli analizzatori statici come PHPStan. I metodi magici sono punti di estensione integrati di PHP per il comportamento dinamico, ma quando vengono abusati, trasformano il codice pulito in un incubo di debug.

---

## 1. Ciclo di vita dell'oggetto: `__construct` & `__destruct`

### __construct()
* **Punto chiave**: Il metodo `__construct` viene chiamato automaticamente quando viene creata una nuova istanza di una classe, fungendo da punto di ingresso per l'inizializzazione dell'oggetto. A partire da **PHP 8.0+**, questo metodo supporta la promozione delle proprietà del costruttore (Constructor Property Promotion) per ridurre drasticamente il codice boilerplate.
* **Perché è importante**: Senza un costruttore, gli oggetti vengono inizializzati in uno stato vuoto, costringendo i sviluppatori a utilizzare setter o mutazioni di proprietà pubbliche, con il rischio di un'inizializzazione incompleta. La promozione delle proprietà del costruttore semplifica il codice combinando la dichiarazione delle proprietà, la tipizzazione dei parametri e l'assegnazione in una singola firma.
* **Esempio**:
  ```php
  // app/DTO/UserSession.php
  namespace App\DTO;

  class UserSession 
  {
      private string $sessionId;

      // PHP 8.0+ Constructor Property Promotion
      public function __construct(
          public string $username,
          protected string $role = 'guest',
          private bool $isActive = true
      ) {
          $this->sessionId = bin2hex(random_bytes(16));
      }

      public function getSessionId(): string 
      {
          return $this->sessionId;
      }
  }
  ```
* **Conseguenza**: La promozione delle proprietà del costruttore elimina il codice boilerplate e garantisce che le classi siano sempre in uno stato valido e tipizzato fin dal momento dell'istanza. Tuttavia, può portare a firme del costruttore troppo cariche se vengono iniettate troppe dipendenze, mascherando violazioni del principio di singola responsabilità (Single Responsibility Principle).

### __destruct()
* **Punto chiave**: Il metodo `__destruct` viene invocato automaticamente quando non ci sono più riferimenti a un oggetto o durante lo spegnimento dello script.
* **Perché è importante**: Fornisce un hook affidabile per rilasciare risorse, come la chiusura di connessioni aperte al database, la scrittura di buffer di log o il rilascio di blocchi a livello di sistema.
* **Esempio**:
  ```php
  // app/Services/FileLogger.php
  namespace App\Services;

  class FileLogger 
  {
      private mixed $handle;

      public function __construct(string $filePath) 
      {
          $this->handle = fopen($filePath, 'a');
      }

      public function log(string $message): void 
      {
          fwrite($this->handle, $message . PHP_EOL);
      }

      public function __destruct() 
      {
          if (is_resource($this->handle)) {
              fclose($this->handle);
          }
      }
  }
  ```
* **Conseguenza**: I distruttori automatizzano la pulizia delle risorse. Tuttavia, poiché PHP utilizza il conteggio dei riferimenti e un garbage collector ciclico, il momento esatto della distruzione dell'oggetto non è deterministico. Se la tua applicazione si affida ai distruttori per rilasciare blocchi di sistema critici e sensibili al tempo, potrebbe incorrere in race conditions (condizioni di concorrenza).

---

## 2. Accessori dinamici (Overloading delle proprietà): `__get`, `__set`, `__isset` & `__unset`

* **Punto chiave**: Questi quattro metodi magici intercettano le operazioni di lettura, scrittura, verifica dell'esistenza (`isset()`) e distruzione (`unset()`) sulle proprietà che sono non definite o inaccessibili (ad es., private/protected) dall'ambito corrente.
* **Perché è importante**: Consentono alle classi di agire come contenitori di dati flessibili e dinamici. Gli ORM dei framework (come Eloquent di Laravel) si affidano all'overloading delle proprietà per mappare dinamicamente le colonne del database alle proprietà dell'oggetto senza dichiarare ogni colonna come una proprietà di classe hardcoded.
* **Esempio**:
  ```php
  // app/Models/SettingsBag.php
  namespace App\Models;

  class SettingsBag 
  {
      private array $settings = [];

      public function __construct(array $defaultSettings = []) 
      {
          $this->settings = $defaultSettings;
      }

      // Triggered when reading an inaccessible or non-existent property
      public function __get(string $name): mixed 
      {
          return $this->settings[$name] ?? null;
      }

      // Triggered when writing to an inaccessible or non-existent property
      public function __set(string $name, mixed $value): void 
      {
          $this->settings[$name] = $value;
      }

      // Triggered when calling isset() or empty() on inaccessible properties
      public function __isset(string $name): bool 
      {
          return isset($this->settings[$name]);
      }

      // Triggered when calling unset() on inaccessible properties
      public function __unset(string $name): void 
      {
          unset($this->settings[$name]);
      }
  }
  ```
* **Conseguenza**: L'overloading delle proprietà offre un'estrema flessibilità, consentendo una rapida prototipazione. Tuttavia, rompe l'analisi statica. Gli IDE non possono autocompletare queste proprietà e strumenti come PHPStan le segnaleranno come errori a meno che non vengano documentate manualmente utilizzando le annotazioni PHPDoc `@property` a livello di classe.

> [!WARNING]
> **Validazione rigorosa delle firme (PHP 8.0+)**
> Prima di PHP 8.0, le firme dei metodi magici venivano validate in modo blando. A partire da **PHP 8.0+**, PHP impone controlli di tipo rigorosi sui metodi magici se si aggiungono i tipi. Ad esempio, la dichiarazione `__isset(string $name): bool` viene validata e la restituzione di un valore non booleano o la mancata corrispondenza del tipo di parametro genera un errore fatale (Fatal Error) in fase di compilazione.

---

## 3. Invocazione dinamica dei metodi (Overloading dei metodi): `__call` & `__callStatic`

* **Punto chiave**: `__call` intercetta le chiamate a metodi dell'oggetto non definiti o inaccessibili, mentre `__callStatic` intercetta le chiamate a metodi statici non definiti o inaccessibili.
* **Perché è importante**: Questi metodi facilitano i pattern proxy, decorator e le architetture facade. Acquisendo i nomi dei metodi e gli argomenti a runtime, è possibile inoltrare le chiamate ai servizi sottostanti, registrare le esecuzioni in modo dinamico o creare fluent API builder.
* **Esempio**:
  ```php
  // app/Services/MetricsCollector.php
  namespace App\Services;

  class MetricsCollector 
  {
      public function __construct(
          private object $service
      ) {}

      // Intercepts instance method calls
      public function __call(string $name, array $arguments): mixed 
      {
          if (!method_exists($this->service, $name)) {
              throw new \BadMethodCallException("Method {$name} does not exist.");
          }

          $start = microtime(true);
          $result = call_user_func_array([$this->service, $name], $arguments);
          $duration = microtime(true) - $start;

          // Log performance metrics
          error_log("Service method {$name} took " . round($duration, 4) . "s to execute.");

          return $result;
      }

      // Intercepts static method calls
      public static function __callStatic(string $name, array $arguments): mixed 
      {
          return "Static method '{$name}' called with arguments: " . json_encode($arguments);
      }
  }
  ```
* **Conseguenza**: L'overloading dei metodi consente astrazioni di API eleganti e pulite (come le Facade di Laravel). Tuttavia, il debug degli stack di chiamata (call stack) diventa difficile perché le tracce dello stack vengono instradate attraverso il gestore magico. Inoltre, gli strumenti di analisi statica richiedono tag PHPDoc `@method` espliciti per evitare la segnalazione di errori.

---

## 4. Trasformazioni e comportamenti degli oggetti: `__toString`, `__invoke` & `__clone`

* **Punto chiave**: Questi metodi controllano il modo in cui gli oggetti interagiscono con i costrutti principali del linguaggio PHP: conversione in stringa, sintassi di esecuzione delle funzioni e clonazione.
* **Perché è importante**:
  - `__toString()` consente agli oggetti di rappresentarsi come stringhe (ad es., il rendering di un Value Object come un indirizzo email o una rappresentazione di valuta).
  - `__invoke()` rende un oggetto eseguibile come se fosse una funzione, abilitando pattern single-action.
  - `__clone()` intercetta la clonazione degli oggetti, consentendo la copia profonda (deep copy) delle risorse nidificate invece di riferimenti superficiali.
* **Esempio**:
  ```php
  // app/ValueObjects/EmailAddress.php
  namespace App\ValueObjects;

  class EmailAddress 
  {
      public function __construct(
          public string $email
      ) {
          if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
              throw new \InvalidArgumentException("Invalid email format.");
          }
      }

      // String representation - makes this class Stringable
      public function __toString(): string 
      {
          return $this->email;
      }
  }

  // app/Services/JobRunner.php
  namespace App\Services;

  class JobRunner 
  {
      public \DateTimeImmutable $lastRun;

      public function __construct() 
      {
          $this->lastRun = new \DateTimeImmutable();
      }

      // Makes the class callable as a function
      public function __invoke(string $taskName): string 
      {
          return "Executing task '{$taskName}' at {$this->lastRun->format('Y-m-d')}";
      }

      // Defines custom cloning behavior for deep copy
      public function __clone() 
      {
          // Clone internal object references to prevent shared state
          $this->lastRun = new \DateTimeImmutable();
      }
  }
  ```
* **Conseguenza**:
  - **Implementazione implicita dell'interfaccia**: A partire da **PHP 8.0+**, qualsiasi classe che implementi `__toString()` implementa automaticamente l'interfaccia nativa `Stringable`, consentendo il type hinting come `string|\Stringable`.
  - **Pattern funzionali**: L'implementazione di `__invoke` consente agli oggetti di essere passati direttamente dove sono richiesti argomenti `callable`.
  - **Bug di riferimento**: Le operazioni standard di `clone` eseguono copie superficiali. Se un oggetto ha sotto-oggetti e non si scrive un gestore `__clone` personalizzato, la modifica delle proprietà sull'oggetto clonato muterà accidentalmente anche i sotto-oggetti dell'oggetto originale.

---

## 5. Serializzazione degli oggetti: L'evoluzione della persistenza dello stato

* **Punto chiave**: I metodi magici di serializzazione gestiscono il modo in cui un oggetto si converte in una stringa lineare tramite `serialize()` e ripristina il suo stato tramite `unserialize()`.
* **Perché è importante**: Alcune proprietà, come le connessioni al database non elaborate, gli handle delle risorse del client HTTP o le credenziali di sicurezza, non possono o non devono essere serializzate. Personalizzando la serializzazione, si controlla esattamente quali variabili vengono persistite e come i collegamenti vengono ricostruiti al risveglio.
* **Esempio**:
  ```php
  // app/Services/SearchClient.php
  namespace App\Services;

  class SearchClient 
  {
      private mixed $connection; // Resource handle that cannot be serialized

      public function __construct(
          private string $host,
          private int $port,
          public array $options = []
      ) {
          $this->connect();
      }

      private function connect(): void 
      {
          // Simulate a network connection initialization
          $this->connection = "Socket connected to {$this->host}:{$this->port}";
      }

      // MODERN APPROACH: Available since PHP 7.4+
      public function __serialize(): array 
      {
          // Return an array representing the object state
          return [
              'host' => $this->host,
              'port' => $this->port,
              'options' => $this->options,
          ];
      }

      public function __unserialize(array $data): void 
      {
          $this->host = $data['host'];
          $this->port = $data['port'];
          $this->options = $data['options'];

          // Re-establish connection automatically
          $this->connect();
      }

      /* 
       * Legacy Sleep & Wakeup (Pre-PHP 7.4)
       * Note: If __serialize() and __unserialize() exist, 
       * PHP 7.4+ will ignore __sleep() and __wakeup() entirely.
       */
      public function __sleep(): array 
      {
          // Must return an array of property names
          return ['host', 'port', 'options'];
      }

      public function __wakeup(): void 
      {
          $this->connect();
      }
  }
  ```
* **Conseguenza**:
  - **Perché __serialize/__unserialize vince (PHP 7.4+)**: Il metodo legacy `__sleep` restituisce un array di nomi di proprietà, il che è restrittivo perché non è possibile modificare o filtrare le strutture dati inline. Il moderno `__serialize` restituisce un array associativo arbitrario. Ciò consente di formattare i dati, escludere array nidificati profondi o serializzare valori dinamici personalizzati senza dichiararli come proprietà di classe.
  - **Priorità**: Se in una classe sono presenti sia i metodi di serializzazione legacy che quelli moderni, PHP 7.4+ eseguirà i moderni metodi `__serialize` e `__unserialize`, ignorando completamente `__sleep` e `__wakeup`.

---

## 6. Compromessi architettonici e limitazioni

Sebbene i metodi magici consentano una sintassi elegante e pulita, comportano costi reali sostanziali. Gli sviluppatori devono valutare questi compromessi prima di utilizzarli.

### 1. Degrado delle prestazioni
I metodi magici vengono risolti a runtime e non possono essere ottimizzati efficacemente dall'engine OPcache di PHP. L'accesso diretto a una proprietà pubblica è molto più veloce rispetto all'instradamento dell'accesso tramite `__get` e `__set`. La chiamata a `__call` comporta un sovraccarico dovuto al packaging in array degli argomenti e all'instradamento dinamico dell'esecuzione.

| Operazione | Velocità relativa | Impatto sui cicli grandi |
| :--- | :--- | :--- |
| Accesso diretto alla proprietà | **1.0x (Il più veloce)** | Trascurabile |
| Metodo magico `__get` / `__set` | **Da 3.5x a 4.0x più lento** | Utilizzo della CPU misurabile |
| Chiamata diretta al metodo | **1.0x (Il più veloce)** | Trascurabile |
| Metodo magico `__call` | **Da 2.5x a 3.0x più lento** | Alto sovraccarico di CPU |

### 2. Analisi statica e autocompletamento IDE
Lo sviluppo moderno di PHP dipende fortemente dall'analisi statica (PHPStan, Psalm) per individuare i bug prima che il codice raggiunga la produzione. Poiché i metodi magici nascondono le definizioni delle proprietà e le firme dei metodi, questi strumenti non possono verificare la correttezza del codice. Senza tag PHPDoc `@property` e `@method` completi, si perde l'autocompletamento, gli strumenti di refactoring e la sicurezza dei tipi.

### 3. Rischi di sicurezza (Object Injection)
La deserializzazione di input utente non attendibili tramite `unserialize()` è una pericolosa vulnerabilità in PHP. Quando PHP istanzia un oggetto tramite `unserialize`, chiama `__wakeup` o `__unserialize`. Gli utenti malintenzionati possono costruire payload serializzati (chiamati POP chain) che sfruttano il codice all'interno di questi metodi magici per eseguire comandi shell arbitrari (Remote Code Execution).

---

## Quiz di autovalutazione

Metti alla prova la tua comprensione dei metodi magici di PHP. Scegli la tua risposta ed espandi il menu a discesa per verificarla.

### Domanda 1: In che modo PHP 8.0+ gestisce metodi di serializzazione in conflitto?
Se una classe implementa contemporaneamente `__sleep()`, `__wakeup()`, `__serialize()` e `__unserialize()`, cosa succede in PHP 7.4+?
- A) PHP genera un Fatal Error a causa di dichiarazioni di serializzazione duplicate.
- B) PHP esegue `__serialize()` e `__unserialize()` e ignora i metodi legacy.
- C) PHP unisce i risultati di entrambi i metodi di serializzazione.

<details>
<summary>Clicca per vedere la risposta</summary>

**Risposta: B**  
A partire da PHP 7.4+, l'engine dà la priorità a `__serialize()` e `__unserialize()`. Se questi metodi moderni sono presenti, i metodi legacy `__sleep()` e `__wakeup()` vengono ignorati.
</details>

### Domanda 2: Cosa succede quando si tenta di assegnare un valore a una proprietà inesistente se `__set` non è definito?
- A) PHP genera una `RuntimeException`.
- B) PHP genera un `TypeError`.
- C) PHP crea dinamicamente una proprietà pubblica sull'istanza dell'oggetto.

<details>
<summary>Clicca per vedere la risposta</summary>

**Risposta: C**  
In PHP, se una proprietà non è definita nella classe e non è implementato il metodo magico `__set`, l'assegnazione di un valore come `$object->foo = 'bar'` farà sì che PHP crei dinamicamente una proprietà pubblica sull'istanza. *(Nota: le proprietà dinamiche sono deprecate in PHP 8.2+ e genereranno una notifica di deprecazione a meno che la classe non sia contrassegnata con `#[AllowDynamicProperties]`)*.
</details>

### Domanda 3: Quale interfaccia viene implementata automaticamente in PHP 8.0+ quando una classe definisce un metodo `__toString()`?
- A) `Serializable`
- B) `Stringable`
- C) `JsonSerializable`

<details>
<summary>Clicca per vedere la risposta</summary>

**Risposta: B**  
A partire da PHP 8.0+, qualsiasi classe che definisce il metodo `__toString()` implementa implicitamente l'interfaccia `Stringable`, il che le consente di superare i controlli di tipo che richiedono `string|Stringable`.
</details>

---

## Consigli pratici

I metodi magici sono uno strumento potente per creare API, librerie e framework altamente flessibili, ma dovrebbero essere usati con parsimonia nello sviluppo di applicazioni generali.

**Checklist delle migliori pratiche**:
1. **Documenta sempre le strutture dinamiche**: Se utilizzi `__get`, `__set` o `__call`, scrivi i corrispondenti tag PHPDoc `@property` e `@method` a livello di classe.
2. **Dai priorità alla serializzazione moderna**: Usa sempre `__serialize` e `__unserialize` invece dei legacy sleep/wakeup nel nuovo codice per PHP 7.4+.
3. **Evita la magia nei percorsi critici**: Tieni i metodi magici lontani dai cicli ad alta iterazione per evitare colli di bottiglia nelle prestazioni.
4. **Imponi controlli di tipo**: A partire da PHP 8.0+, specifica i tipi di parametro e di ritorno per i metodi magici per evitare discrepanze di tipo a runtime.