0Pricing
PHP Academy · Lezione

Casi d'uso e servizi applicativi

Esprimete le azioni di business come casi d'uso indipendenti dai framework.

Casi d'uso e servizi applicativi è una lezione PHP Academy gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento PHP Academy, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso PHP Academy include 4 lezioni in totale.

Che cos'è davvero un caso d'uso

Un caso d'uso (detto anche servizio applicativo o interactor) rappresenta esattamente un'operazione specifica dell'applicazione: Registrare un utente, Effettuare un ordine, Annullare un abbonamento. Coordina entità e porte per soddisfare un'unica intenzione. Soprattutto, è indipendente dai framework: niente Request, niente Response, niente helper globali, ma solo semplice codice PHP che potete chiamare da qualsiasi punto.

DTO di comando e di risultato

Un caso d'uso riceve in ingresso un DTO di comando immutabile e restituisce un DTO di risultato. I DTO sono semplici contenitori di dati: non contengono comportamento né logica di convalida oltre alla struttura. Le proprietà readonly (PHP 8.1+) li rendono a prova di manomissione.

<?php
final class RegisterUserCommand
{
    public function __construct(
        public readonly string $email,
        public readonly string $plainPassword,
    ) {}
}

final class RegisterUserResult
{
    public function __construct(public readonly string $userId) {}
}

Il corpo del servizio applicativo

Il servizio traduce il comando in operazioni di dominio. Si occupa dell'orchestrazione a livello applicativo — controlli di unicità, persistenza e restituzione degli identificatori — delegando le regole alle entità.

<?php
final class RegisterUser
{
    public function __construct(
        private Users $users,
        private PasswordHasher $hasher,
    ) {}

    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        if ($this->users->existsByEmail($c->email)) {
            throw new EmailAlreadyRegistered($c->email);
        }
        $user = User::register(
            UserId::generate(),
            new Email($c->email),
            $this->hasher->hash($c->plainPassword),
        );
        $this->users->add($user);
        return new RegisterUserResult((string) $user->id());
    }
}

Mantenere la logica nelle entità

Fate attenzione al modello di dominio anemico: entità ridotte a getter e setter, mentre tutta la logica risiede nei servizi. Gli invarianti appartengono all'entità. Il caso d'uso dovrebbe essere simile a un breve copione di intenzioni, non a una parete di regole di business.

<?php
final class User
{
    private function __construct(
        private UserId $id,
        private Email $email,
        private string $passwordHash,
        private bool $active = false,
    ) {}

    public static function register(UserId $id, Email $e, string $hash): self {
        return new self($id, $e, $hash); // invariants enforced here
    }
    public function activate(): void {
        if ($this->active) throw new AlreadyActive();
        $this->active = true;
    }
    public function id(): UserId { return $this->id; }
}

Confini delle transazioni

Un caso d'uso è il naturale confine della transazione: un caso d'uso equivale a un'unità di lavoro coerente. Invece di disseminare i servizi di chiamate a beginTransaction(), racchiudeteli in un decorator transazionale, così il nucleo rimane indipendente dalla persistenza.

<?php
interface TransactionManager {
    public function transactional(callable $work): mixed;
}

final class TransactionalRegisterUser
{
    public function __construct(
        private RegisterUser $inner,
        private TransactionManager $tx,
    ) {}

    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        return $this->tx->transactional(fn() => ($this->inner)($c));
    }
}

Dove collocare la convalida

Suddividete la convalida in due livelli:

  • La convalida dell'input (formato, campi obbligatori) avviene nell'adattatore di ingresso o in un validator dedicato, prima dell'esecuzione del caso d'uso.
  • La convalida di dominio (invarianti, regole di business) risiede negli oggetti valore e nelle entità, che generano eccezioni di dominio.

Il caso d'uso dà per scontato che l'input sia ben formato e ne fa rispettare il significato.

<?php
final class Email
{
    public function __construct(public readonly string $value) {
        if (!filter_var($value, FILTER_VALIDATE_EMAIL)) {
            throw new InvalidArgumentException("Invalid email: $value");
        }
    }
}

try { new Email('nope'); } catch (Throwable $e) { echo $e->getMessage(), PHP_EOL; }
echo (new Email('a@b.com'))->value, PHP_EOL;

Restituire l'output senza HTTP

Esistono due schemi per restituire dati mantenendo il codice indipendente dai framework:

  • Restituire un DTO di risultato (semplice e sincrono).
  • Porta di output / presenter: il caso d'uso invia il risultato a un confine di output iniettato, lasciando che sia l'adattatore a decidere la formattazione (JSON, HTML, CLI). In questo modo persino la forma della risposta rimane al di fuori del nucleo.
<?php
interface RegisterUserOutput {
    public function present(RegisterUserResult $r): void;
}

final class RegisterUserWithPresenter {
    public function __construct(private Users $users, private PasswordHasher $h) {}
    public function __invoke(RegisterUserCommand $c, RegisterUserOutput $out): void {
        $user = User::register(UserId::generate(), new Email($c->email), $this->h->hash($c->plainPassword));
        $this->users->add($user);
        $out->present(new RegisterUserResult((string) $user->id()));
    }
}

Eventi di dominio dai casi d'uso

I casi d'uso spesso registrano eventi di dominio generati dalle entità, per poi inviarli dopo il commit della transazione. In questo modo gli effetti collaterali (inviare un'email di benvenuto, aggiornare il modello di lettura) vengono disaccoppiati dal flusso di lavoro principale.

<?php
trait RecordsEvents {
    private array $events = [];
    protected function record(object $e): void { $this->events[] = $e; }
    public function releaseEvents(): array {
        $e = $this->events; $this->events = []; return $e;
    }
}

final class UserRegistered {
    public function __construct(public readonly string $userId) {}
}

// Use case calls $user->releaseEvents() and hands them to a dispatcher
echo 'event recorded pattern', PHP_EOL;

Una classe per ogni caso d'uso

Preferite una classe a singola azione (un metodo pubblico, spesso __invoke) a un servizio monolitico con dieci metodi. Vantaggi:

  • Responsabilità singola e denominazione chiare (CancelSubscription, non SubscriptionService::cancel).
  • Il costruttore inietta solo ciò di cui questa operazione ha bisogno.
  • È facile applicare decorator (transazione, logging, autorizzazione).

Configurare la composition root

Il caso d'uso non istanzia mai con new le proprie dipendenze: se ne occupa la composition root. Ecco una configurazione manuale che potreste inserire nella definizione di un container DI.

<?php
$pdo      = new PDO('sqlite::memory:');
$users    = new PdoUsers($pdo);
$hasher   = new BcryptHasher();
$register = new RegisterUser($users, $hasher);

// Decorate with a transaction boundary
$register = new TransactionalRegisterUser($register, new PdoTransactionManager($pdo));

// Driving adapter calls it
$result = $register(new RegisterUserCommand('dev@coddykit.com', 's3cret!'));
echo $result->userId, PHP_EOL;

Aspetti trasversali tramite decorator

Logging, metriche e autorizzazione sono aspetti trasversali: manteneteli fuori dal corpo del caso d'uso. Racchiudete il servizio in decorator che ne condividono l'interfaccia, così il nucleo rimane concentrato sul flusso di lavoro mentre gli aspetti infrastrutturali vengono composti attorno a esso.

<?php
interface RegisterUserHandler {
    public function __invoke(RegisterUserCommand $c): RegisterUserResult;
}

final class LoggingRegisterUser implements RegisterUserHandler {
    public function __construct(
        private RegisterUserHandler $inner,
        private LoggerInterface $log,
    ) {}
    public function __invoke(RegisterUserCommand $c): RegisterUserResult {
        $this->log->info('register.start', ['email' => $c->email]);
        $r = ($this->inner)($c);
        $this->log->info('register.ok', ['id' => $r->userId]);
        return $r;
    }
}

Verifica rapida

Dove dovrebbe risiedere la regola "un'email deve essere univoca e ben formata"?

Riepilogo

I casi d'uso indipendenti dai framework offrono un livello applicativo pulito:

  • Una classe a singola azione per ogni operazione, che riceve un DTO di comando e restituisce un DTO di risultato (oppure invia il risultato a una porta di output).
  • Le entità e gli oggetti valore possiedono gli invarianti; il servizio si limita a orchestrare: evitate i modelli anemici.
  • I casi d'uso sono confini delle transazioni, racchiusi in decorator anziché contenere chiamate inline a beginTransaction.
  • Separate la convalida dell'input (adattatore/VO) dalla convalida di dominio (entità).
  • Gli eventi di dominio disaccoppiano gli effetti collaterali; la composition root collega le dipendenze.

Domande Frequenti

La lezione «Casi d'uso e servizi applicativi» è gratuita?

Sì — il testo completo di «Casi d'uso e servizi applicativi» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso PHP Academy, passa a CoddyKit PRO. Il corso PHP Academy include 4 lezioni in totale.

Cosa imparerò in «Casi d'uso e servizi applicativi»?

Esprimete le azioni di business come casi d'uso indipendenti dai framework. Eserciti PHP Academy con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare PHP Academy?

Non è richiesta alcuna esperienza precedente. PHP Academy su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Casi d'uso e servizi applicativi»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione PHP Academy?

Sì. Ogni lezione PHP Academy include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Dall'architettura a livelli alla Clean Architecture
  2. Porte e adapter spiegati
  3. Casi d'uso e servizi applicativi
  4. Inversione delle dipendenze nella pratica
← Torna a PHP Academy