Il problema: controller che fanno troppo
Un OrderController ha un metodo store() che: valida l'input, verifica la disponibilita dei prodotti, calcola il totale con sconti, crea l'ordine nel database, scala l'inventario, invia l'email di conferma, notifica il magazzino, aggiorna le statistiche. Sono 80 righe di codice procedurale in un metodo del controller. Funziona, ma non e riutilizzabile: se devi creare ordini da un'API, da un import CSV, o da un job schedulato, devi duplicare la logica.
Il Service Layer e lo strato architetturale che contiene la logica di business dell'applicazione. Ogni operazione significativa — creare un ordine, processare un pagamento, generare un report — ha un Service dedicato che incapsula la logica, indipendente dal meccanismo di input (HTTP, CLI, queue).
Struttura del Service Layer
Un Service Layer tipico si posiziona tra i controller e il layer di persistenza:
- Controller → riceve la request, valida l'input, chiama il Service
- Service → contiene la logica di business, coordina le operazioni, chiama Repository/Model
- Repository/Model → accede ai dati, persiste le modifiche
Il Service non sa nulla di HTTP: non riceve Request, non restituisce Response. Riceve dati primitivi o DTO e restituisce risultati o lancia eccezioni. Questo lo rende invocabile da qualsiasi contesto.
Esempio pratico: ArticleService in Soft PHP MVC
Nel framework, l'ArticleService centralizza la logica degli articoli:
-
getPublished(int $page, int $perPage): PaginatedResult— articoli pubblicati con paginazione -
getBySlug(string $slug): ?Article— singolo articolo per slug -
getByTag(string $tag): array— articoli filtrati per tag -
getRelated(Article $article, int $limit): array— articoli correlati
Il BlogController chiama $this->articleService->getPublished($page, 12) e passa il risultato alla View. L'ApiController chiama lo stesso metodo e restituisce JSON. Il comando CLI sitemap:generate chiama lo stesso metodo per generare la sitemap. Una logica, tre contesti diversi.
Service vs Helper vs Utility
-
Service: contiene logica di business del dominio. Spesso stateful (tiene dipendenze iniettate).
OrderService,PaymentService. -
Helper: funzioni di utilita generiche senza stato.
Str::slug(),Path::resolve(). Non contengono logica di business. -
Utility: simile a Helper ma più specifico. Spesso classi statiche con un singolo scopo.
ImageResizer,CsvParser.
La differenza chiave: un Service conosce il dominio. Sa cosa significa "creare un ordine" con tutte le regole che comporta. Un Helper non sa nulla del dominio — manipola stringhe, file, date.
Quando introdurre un Service
Non ogni applicazione ha bisogno di un Service Layer. Ecco le regole pratiche:
- Serve un Service quando la stessa logica viene usata da più controller o da contesti diversi (web, CLI, API)
- Serve un Service quando il metodo del controller supera le 20 righe di logica di business
- Serve un Service quando la logica deve essere testata unitariamente senza HTTP
-
Non serve un Service per CRUD banali dove il controller fa solo
Model::find($id)e passa alla view - Non serve un Service se la logica e specifica di un solo controller e non verra mai riutilizzata
Pattern di naming e organizzazione
I Service seguono convenzioni chiare:
-
Nome:
{Dominio}Service—ArticleService,OrderService,UserService -
Metodi: verbi che descrivono azioni di business —
createOrder(),cancelSubscription(), nonhandle()oprocess() -
Directory:
app/Services/— separati da Controller, Model e Helper - Dipendenze: iniettate nel costruttore — Repository, Mailer, EventDispatcher
Il Service Layer non e un pattern complesso: e buon senso architetturale. Separare "cosa entra e cosa esce" (controller) da "cosa succede nel mezzo" (service) e il refactoring più utile che puoi fare in un'applicazione PHP che sta crescendo.
Top comments (0)