Introduzione
In un progetto PHP reale non esistono soltanto le classi usate dall’applicazione. Spesso sono presenti anche test automatici, script di manutenzione, fixture e strumenti interni. Se tutti questi file vengono organizzati nello stesso spazio dei nomi, il progetto diventa più difficile da comprendere e aumentano i rischi di collisione tra classi.
Un approccio pratico consiste nel separare il codice di produzione da quello di test attraverso namespace distinti e configurazioni di autoloading specifiche. In questo modo una classe dell’applicazione appartiene, per esempio, al namespace App, mentre una classe di test appartiene al namespace Tests. Composer può caricare automaticamente entrambi, mantenendo però una struttura chiara.
Questo schema è utile anche quando si desidera impedire accidentalmente che il codice applicativo dipenda da una classe presente soltanto nei test. La separazione logica non sostituisce le regole di progettazione, ma rende più evidenti gli errori e migliora la manutenzione.
Codice completo
Immaginiamo questa struttura di progetto:
negozio/
├── composer.json
├── src/
│ ├── Catalogo/
│ │ └── Prodotto.php
│ └── Sconto/
│ └── CalcolatoreSconto.php
├── tests/
│ └── Catalogo/
│ └── ProdottoTest.php
└── public/
└── index.php Il file composer.json definisce percorsi e namespace:
{
"name": "esempio/negozio",
"require": {
"php": "^8.1"
},
"require-dev": {
"phpunit/phpunit": "^10.0"
},
"autoload": {
"psr-4": {
"App": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests": "tests/"
}
}
} La classe di produzione src/Catalogo/Prodotto.php contiene:
<?php
declare(strict_types=1);
namespace AppCatalogo;
final class Prodotto
{
public function __construct(
private string $nome,
private float $prezzo
) {
}
public function nome(): string
{
return $this->nome;
}
public function prezzo(): float
{
return $this->prezzo;
}
} Il servizio che applica uno sconto si trova in src/Sconto/CalcolatoreSconto.php:
<?php
declare(strict_types=1);
namespace AppSconto;
use AppCatalogoProdotto;
final class CalcolatoreSconto
{
public function prezzoScontato(Prodotto $prodotto, float $percentuale): float
{
if ($percentuale < 0 || $percentuale > 100) {
throw new InvalidArgumentException(
´La percentuale deve essere compresa tra 0 e 100.´
);
}
return $prodotto->prezzo() * (1 - $percentuale / 100);
}
} Il test appartiene invece al namespace Tests:
<?php
declare(strict_types=1);
namespace TestsCatalogo;
use AppCatalogoProdotto;
use PHPUnitFrameworkTestCase;
final class ProdottoTest extends TestCase
{
public function testIlProdottoConservaNomeEPrezzo(): void
{
$prodotto = new Prodotto(´Tastiera´, 49.90);
self::assertSame(´Tastiera´, $prodotto->nome());
self::assertSame(49.90, $prodotto->prezzo());
}
} Il file pubblico può usare le classi dell’applicazione senza includere manualmente ogni file:
<?php
declare(strict_types=1);
require dirname(__DIR__) . ´/vendor/autoload.php´;
use AppCatalogoProdotto;
use AppScontoCalcolatoreSconto;
$prodotto = new Prodotto(´Tastiera´, 49.90);
$calcolatore = new CalcolatoreSconto();
echo $calcolatore->prezzoScontato($prodotto, 10); Dopo aver creato o modificato composer.json, bisogna generare l’autoloader:
composer install
composer dump-autoload I test possono essere eseguiti con:
vendor/bin/phpunit tests Spiegazione
La chiave autoload descrive le classi necessarie all’applicazione in esecuzione. La relazione "App": "src/" stabilisce che il namespace AppCatalogo viene cercato nella directory src/Catalogo. Di conseguenza, la classe AppCatalogoProdotto deve trovarsi nel file src/Catalogo/Prodotto.php.
La chiave autoload-dev viene usata per le dipendenze e le classi necessarie durante lo sviluppo. Il namespace Tests viene associato alla directory tests/. Le classi di test non devono essere caricate nell’ambiente di produzione, perché non fanno parte del comportamento dell’applicazione.
Il comando composer install --no-dev installa soltanto le dipendenze necessarie all’esecuzione. È quindi una buona scelta per il server di produzione, dove PHPUnit e le classi di test non servono:
composer install --no-dev --optimize-autoloader In ogni script eseguito dall’esterno, è sufficiente includere una volta vendor/autoload.php. Da quel momento Composer registra l’autoloader e PHP può risolvere automaticamente le classi richieste.
Best practice
- Usa namespace distinti: mantieni
Appper il codice applicativo eTestsper i test. - Rispetta la struttura delle cartelle: namespace, sottocartelle e nome del file devono essere coerenti.
- Non includere file manualmente: evita molte istruzioni
requiresparse nel progetto. - Separa le dipendenze di sviluppo: inserisci PHPUnit e strumenti simili in
require-dev. - Rigenera l’autoloader: dopo ogni modifica alla configurazione esegui
composer dump-autoload. - Ottimizza in produzione: usa
--no-deve--optimize-autoloader. - Usa dichiarazioni strette:
declare(strict_types=1)rende più prevedibili i tipi. - Non mettere logica nei test riutilizzabile dall’applicazione: se una funzionalità serve davvero alla produzione, deve appartenere a
src/.
Riepilogo
Namespace e autoloading permettono di separare chiaramente codice applicativo, test e strumenti di sviluppo. La configurazione autoload collega il namespace principale alle classi usate in produzione, mentre autoload-dev mantiene isolato il codice di test.
Questo approccio riduce gli include manuali, rende più leggibile la struttura del progetto e aiuta a creare build di produzione più leggere. La regola fondamentale è semplice: ogni classe deve avere un namespace coerente con la sua posizione e ogni ingresso dell’applicazione deve caricare una sola volta l’autoloader di Composer.
