Il concetto di lazy loading (che tradotto significa letteralmente "caricamento pigro") offerto dalle teorie delle programmazione orientata agli oggetti è davvero interessante e non deve essere sottovalutato. Per capire di cosa si tratta, proviamo ad immaginare una situazione ipotetica in cui abbiamo creato 10 classi differenti e dobbiamo caricarle dalla prima all'ultima all'interno della nostra applicazione. Dovremmo ripetere la procedura di inclusione all'inizio dei nostri script per ogni classe desiderata, in una modalità simile alla seguente:
include "oop/classes/red.php"; include "oop/classes/blue.php"; include "oop/classes/green.php"; include "oop/classes/orange.php"; include "oop/classes/pink.php"; // inclusione di altre classi...
Se andassimo poi ad inizializzare un'istanza della classe White, senza che questa sia stata prima inclusa, il parser PHP genererà ovviamente un Fatal Error:
$white = new White(); // PHP Fatal Error Fatal error: Class 'White' not found in ...
Questo ovviamente accade perché PHP non sa assolutamente cosa sia la classe White né sa dove andare a recuperarla.
La funzione __autoload
Tramite il concetto di lazy loading invece, è possibile fare in modo che le classi (e non solo) vengano caricate solamente al momento necessario, ovvero quando ad esempio creiamo un'istanza con un costruttore non ancora definito.
Tutto questo diventa possibile dichiarando una funzione denominata __autoload
, che consente a PHP un'ultima chance per caricare la classe. Alla funzione __autoload verrà passato un parametro che rappresenta il nome della classe non ancora definita e quest'ultimo ci servirà per andare ad includere l'apposito file che la contiene.
Ecco un esempio molto semplice:
function __autoload($class_name) { include $class_name . '.php'; } $obj = new MyClass1(); $obj2 = new MyClass2();
Come possiamo notare, non esistono dichiarazioni esplicite di inclusione di classi, ma solamente istruzioni di costruzione di istanze. Tuttavia PHP non genererà alcune errore, dato che caricherà al momento opportuno i file MyClass1.php e MyClass2.php, che conterranno le rispettive classi.
Se vogliamo pianificare una situazione più elegante, in cui le classi sono poste nella nostra cartella "oop/classes" con i namespace "<class_name>.php", potremo agire in questo modo:
function __autoload($class_name) { include "oop/classes" . strtolower($class_name) . "_class.php"; } // carica 'oop/classes/white_class.php' $white = new White(); // carica 'oop/classes/blue_class.php' $blue = new Blue();
Come possiamo vedere, non occorrerà più dichiarare l'inclusione di un elevato numero di classi, ma PHP sarà sempre in grado di andare a recuperare il file corretto per l'inclusione. Questo è sicuramente il primo, grande vantaggio offerto dal concetto di lazy loading, ma non dimentichiamo anche l'impatto sulle performance: caricare molte classi senza utilizzarle realmente può portare ad un dispendio di risorse inopportuno, mentre tramite lazy loading sappiamo che solo le classi necessarie (dettate dall'andamento implicito dell'applicazione) verranno caricate.
Directory multiple
Tutto questo a patto di pianificare correttamente e con criteri appropriati i nomi dei file che andremo a creare: come abbiamo potuto notare infatti, l'autoload si basa sul nome della classe, quindi è una logica conseguenza pensare che il file debba contenere in qualche modo questo nome.
Può capitare però che ci siano diversi file di classi nelle directory della nostra applicazione, come ad esempio "oop/classes/<class_name>.php", "oop/classes_extra/<class_name>.php" o ancora "oop/generic/<class_name>.php".
In questo caso potremo aumentare leggermente la complessità della funzione __autoload andando ad inserire dei controlli sull'esistenza dei rispettivi file: se esiste un file con un determinato nome, allora verrà incluso, altrimenti si proveranno diverse soluzioni in ordine di importanza fino ad arrivare alla directory generica.
Ecco l'esempio:
function __autoload($class_name) { $main = "oop/classes" . strtolower($class_name) . "_class.php"; $extra = "oop/classes_extra" . strtolower($class_name) . "_class.php"; $generic = "oop/generic" . strtolower($class_name) . "_class.php"; if( file_exists($main) ) include $main; else if( file_exists($sub) ) include $sub; else if( file_exists($generic) ) include $generic; } // carica 'oop/classes/white_class.php' $white = new White(); // carica 'oop/classes_extra/blue_class.php' $blue = new Blue(); // carica 'oop/generic/green.php' $blue = new Green();
Come possiamo notare, se non verrà trovato il file nella directory principale, verrà provata un'altra directory secondaria ed infine la directory generica. Se al termine di questi controlli PHP non rileva nessun file esistente genererà un Fatal Error.
Non solo classi
Il procedimento di lazy loading in PHP non agisce solamente nel caso delle classi, bensì anche con le interfacce. In una modalità del tutto identica a quella vista in precedenza, quando dichiariamo che una classe deve implementare una determinata interfaccia, verrà data a PHP un'ultima chance di caricare quest'ultima senza l'occorrenza di dichiararne l'inclusione.
Ecco un semplice esempio (le regole sui controlli poste in precedenza sono state omesse per semplicità):
function __autoload($name) { include "oop/interfaces/" . strtolower($name) . "_interface.php"; } // verrà incluso "oop/interfaces/itest.php" class Foo implements ITest { // ... }
Ed ecco ora il tutto riunito con tanto di controllo sulla presenza delle interfacce:
function __autoload($class_name) { $interfaces = "oop/interfaces/" . strtolower($class_name) . "_interface.php"; $main = "oop/classes" . strtolower($class_name) . "_class.php"; $extra = "oop/classes_extra" . strtolower($class_name) . "_class.php"; $generic = "oop/generic" . strtolower($class_name) . "_class.php"; if( file_exists($interfaces) ) include $interfaces; else if( file_exists($main) ) include $main; else if( file_exists($sub) ) include $sub; else if( file_exists($generic) ) include $generic; } // verrà incluso "oop/interfaces/itest.php" class Foo implements ITest { // ... } // carica 'oop/classes/white_class.php' $white = new White(); // carica 'oop/classes_extra/blue_class.php' $blue = new Blue(); // carica 'oop/generic/green.php' $blue = new Green();
Lazy Loading con SPL
L'estensione SPL (Standard PHP Library) fornisce alcuni aiuti interessanti nelle procedure di autoloading o lazy loading.
In particolare, abbiamo a disposizione diverse funzioni che ci permettono di avere un maggiore controllo sulla fase di autoload, consentendoci di registrare (spl_autoload_register), de-registrare (spl_autoload_unregister) e chiamare (spl_autoload_call) le funzioni poste nello stack di autoloading con tanto di possibilità di fornire una moltitudine di estensioni possibili (spl_autoload_extensions).
Per una trattazione completa di questo argomento vi rimando alla pagina ufficiale della documentazione: SPL Functions.
Conclusione
In questo articolo abbiamo analizzato un importante concetto offerto dalla OOP. L'implementazione del lazy loading all'interno delle nostre applicazioni di certo non inciderà sulla qualità complessiva delle stesse, ma fornirà un aiuto non indifferente nelle procedure di testing interne e nella pianificazione delle directory che contengono le nostre gerarchie, senza contare la risoluzione di diversi errori dovuti al caricamento delle classi ed alla riduzione di tempo nella dichiarazione delle inclusioni.
Per un ulteriore approfondimento vi rimandiamo alla pagina Autoloading classes della documentazione ufficiale offerta da PHP.net.