devdev / in the loop

Gestire l’autoloading di una classe con SPL autoloader di PHP

SPL stà per Standard PHP Library, e come ci suggerisce il nome, è una collezioni di interfacce (interface) e classi già pronte da utilizzare, il cui scopo è semplificarci la vita in alcuni contesti, scrivendo meno “codice di base” potendo così dedicarci maggiormente al cuore del progetto che stiamo sviluppando. Uno dei problemi comuni, chiamiamoli così, è quello di dover includere i file che contengono le classi che abbiamo scritto. Facciamo una piccola parentesi: è buona prassi definire una classe per ciascun file, tecnica che oramai è ampiamente diffusa e definita nel dettaglio nello PSR-4 (PHP Standards Reccomendations) al quale, se non lo conoscete, vi consiglio fortemente di dare un’occhiata.

Tornando a noi, abbiamo quindi la necessità di includere i file con le classi e le interface nel nostro progetto, ma dobbiamo anche rispettare l’ordine giusto se non vogliamo incontrare errori. Anziché creare una catena di include()  o require() , possiamo affidarci ad un autoloader, cioè un sistema che permette di reagire al volo quando viene chiamata una classe che non è stata ancora definita. Un autoloader può quindi includere il file (in fase di runtime) che contiene la classe chiamata e continuare l’esecuzione.

Partiamo da un semplice esempio che non utilizza alcun sistema di autoloading:

include 'include/Addressbook.php';
include 'include/Mailer.php';

$addresses = new Addressbook();
$mailer = new Mailer();

Certo, il nostro esempio è facilmente gestibile, ma quando il nostro progetto arriverà a contenere centinaia di classi? Certo è che potremmo scrivere un sacco di include() o require() in cima al nostro progetto, o magari soltanto nei file che sappiamo utilizzeranno quelle classi, ma non sarebbe una soluzione elegante né efficace dal punto di vista delle performance perché obbligheremo PHP ad includere ogni singolo file-classe, anche se non lo utilizziamo. (e avremo un sacco di overhead).

Qui entra in gioco l’autoloader incluso nelle librerie SPL: lo avremo sempre a disposizione dato che è compilato assieme a PHP sin dalla versione 5.3.0 e non può essere disabilitato. Vedremo in questa mini guida come utilizzarlo con l’esempio che abbiamo già introdotto: inviare una mail.

Partiamo dalla funzione principale, che probabilmente è l’unica che ci servirà: spl_autoloader_register() . Questa funzione aggiunge un elemento alla coda (o ne crea una) di caricamento, e in pratica ci lascia decidere cosa succede quando nel nostro progetto viene usata una classe che non è stata ancora definita, solitamente perché il file nella quale è definita non è stato ancora incluso. Il primo parametro di spl_autoloader_register() è di tipo callable, dovremmo passargli cioè una funzione già definita, una funzione anonima oppure ancora un metodo statico di una classe. A quest’ultima, PHP passerà come parametro il nome della classe invocata che non è stata ancora definita. Vediamo un esempio:

function caricaFileClasse($class) {
    include "include/"$class.php";
    echo "chiamata la classe $class";
}

spl_autoload_register('caricaFileClasse');

$prova = new Prova;

// chiamata la classe Prova

Per prima cosa, definiamo la funzione di caricamento caricaFileClasse(), che si occuperà realmente di includere il file nel quale abbiamo dichiarato la classe. Successivamente, diciamo all’autoloader SPL di utilizzarla. Quando proviamo a creare un’istanza della classe Prova , che non è definita, PHP chiamerà caricaFileClasse(). Naturalmente, possiamo creare una logica più complessa, ma in questo esempio, ci limiteremo appunto ad includere il file che prende il nome della classe nella directory include . Prima vi dicevo che è possibile anche indicare un metodo statico:

class ClasseProva {
    public static function carica($class)
    {
         // ....
    }
}

spl_autoload_register('ClasseProva::carica');

oppure una semplice funzione anonima:

spl_autoload_register(function($class) {
    // ...
});

Capite le basi, applichiamo questo sistema al nostro progetto di invio email. Per farlo, daremo per scontato che ci sia un file di partenza, bootstrap.php , nel quale viene definito e gestito l’autoloading delle classi, ricalcando l’esempio di prima:

<?php
// bootstrap.php

function includeClassFile($class) {
    $file = "include/" . strtolower( trim( $class ) ) . ".php";
    if (file_exists($file)
    {
        // Il file esiste, lo includiamo
        include $file;
        return true;
    }
    return false;
}

spl_autoload_register('includeClassFile');

A questo punto, vediamo le altre classi, AddressBook e Mailer:

<?php
// include/Addressbook.php

class Addressbook
{
    private $email;
    private $name;

    public function __construct($email, $name = null)
    {
        $this->email = $email;
        $this->name = $name;
    }

    public function getEmail()
    {
        return $this->email;
    }

    public function getName()
    {
        return $this->name;
    }
}
<?php
// include/Mailer.php

class Mailer
{
    private $_recipients;
    public function addRecipient($email)
    {
        $recipient = new Addressbook($email);
        $this->_recipients[] = $recipient;
    }
    public function send($message)
    {
        foreach ($this->_recipients as $recipient) {
            mail($recipient->getEmail(), 'Oggetto', $message);
            echo echo "Email inviata a". $recipient->getEmail();
        }
    }
}

Vediamo che nella classe Mailer  utilizziamo la classe Addressbook  senza aver incluso in precedenza il file Addressbook.php . Questo è possibile proprio perché nel file bootstrap.php  abbiamo definito un sistema di autoloading. Detto in poche parole, il processo che avviene è questo:

  • viene provato il caricamento/definizione della classe
  • se non esiste, viene chiamato l’autoloader oppure il primo degli autoloader se ce ne sono più di uno
  • se la classe non viene ancora trovata, viene generata un eccezione

Dopo aver definito autoloader e classi, vediamo come potrebbe essere la pagina di invio email send.php :

<?php
// send.php

// includiamo, per primo, il bootstrap che toniente l'autoloader
include "bootstrap.php";

$mailer = new Mailer;
$mailer->addRecipient('prova1@prova.com');
$mailer->addRecipient('prova2@prova.com');
$mailer->addRecipient('prova3@prova.com');

$mailer->send('Questa è una mail di prova');

Come funziona?

L’unico file che abbiamo incluso in send.php  è bootstrap.php , che si occupa di registrare e gestire l’autoloading delle classi, come abbiamo visto. Quando creiamo una nuova istanza di Mailer , PHP vede che non c’è alcuna classe dichiarata con questo nome e chiama immediatamente l’autoloader passandogli come parametro il nome della classe: internamente avviene in automatico questo: includeClassFile(“Mailer”) . A questo punto la nostra funzione include nel progetto il file include/Mailer.php  dove abbiamo definito la classe Mailer  e l’esecuzione può continuare.

Ma non è finita. Nella classe Mailer  viene istanziata la classe AddressBook  ..che non è definita da nessuna parte! Anche qui viene chiamato l’autoloader che include il file include/AddressBook.php  e.. ci siamo capiti.

Le classi quindi vengono caricate in runtime e non dovremo ricorrere ad una sequenza di include()  o require() .

Ogni framework che si rispetti oggi utilizza internamente un autoloader, quindi è essenziale avere una conoscenza, anche basilare, di come esso funziona e cominciare magari a scrivere il proprio.

Gestire gli errori

Il secondo parametro di spl_autoload_register()  è di tipo bool , e dice all’autoloader se deve essere generato un errore se la funzione di autoloading che abbiamo indicato non riesce a caricare la classe richiesta. Vediamo un esempio:

function includiClasse($class) {
    echo $class;
    // non carichiamo alcun file
}

spl_autoload_register('includiClasse');

$prova = new Prova;

Provando a creare un’istanza della classe Prova  (che non è definita), la nostra funzione includiClasse()  non carica alcun file, ma esegue un semplice echo . A questo punto la classe Prova continua a non esistere e PHP genera un Fatal error:

Fatal error: Class 'Prova' not found in xxxx
Questo articolo ti è stato utile?
PHP – LETTURA 6 MINUTI PHP 8: l’operatore null-safe
L’introduzione dell’operatore null coalescing con PHP 7 è stato un buon passo avanti per la pulizia del codice, ma non…
Laravel – LETTURA 4 MINUTI Aggiungere phpMyAdmin a Laravel Sail
Anche se a questo punto lo sapete già, è giusto dire che phpMyAdmin è sicuramente il tool dbms open  source…
PHP – LETTURA 4 MINUTI Usare glob() in PHP per elencare i file di una directory
Spesso abbiamo utilizzato (lo so, l’avete fatto anche voi) la combinazione di opendir(), readdir() e closedir() per elencare i file…
PHP – LETTURA 5 MINUTI Installare PHP su macOS Monterey con homebrew (anche su M1)
L’ultima versione di macOS 12 Monterey non porta più con sé preinstallato PHP. Difatti, una nota nel conf http.conf di…
Roba figa da
if (weekend) {
    relax();
}
la nostra newsletter, ogni tanto.