0Pricing
PHP Academy · Leçon

Cas d’utilisation et services applicatifs

Exprimez les actions métier sous forme de cas d’utilisation indépendants des frameworks.

Cas d’utilisation et services applicatifs est une leçon PHP Academy gratuite sur CoddyKit. Ceci est la leçon 3 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage PHP Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours PHP Academy comprend 4 leçons au total.

Ce qu’est réellement un cas d’utilisation

Un cas d’utilisation (aussi appelé service applicatif ou interacteur) représente exactement une opération propre à l’application : Inscrire un utilisateur, Passer une commande, Annuler un abonnement. Il coordonne les entités et les ports pour accomplir une intention unique. Surtout, il est indépendant de tout framework : pas de Request, pas de Response, pas d’assistants globaux — simplement du PHP ordinaire que vous pouvez appeler depuis n’importe où.

DTO de commande et de résultat

Un cas d’utilisation reçoit en entrée un DTO de commande immuable et renvoie un DTO de résultat. Les DTO sont de simples conteneurs de données — aucun comportement et aucune logique de validation au-delà de leur structure. Les propriétés en lecture seule (PHP 8.1+) les rendent inviolables.

<?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) {}
}

Le corps du service applicatif

Le service traduit la commande en opérations du domaine. Il assure l’orchestration au niveau applicatif — vérification de l’unicité, persistance et retour des identifiants — tout en déléguant les règles aux entités.

<?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());
    }
}

Conservez la logique dans les entités

Méfiez-vous du modèle de domaine anémique : des entités réduites à des accesseurs et mutateurs, tandis que toute la logique se trouve dans les services. Les invariants appartiennent à l’entité. Le cas d’utilisation devrait se lire comme un court scénario d’intentions, et non comme un mur de règles métier.

<?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; }
}

Limites des transactions

Un cas d’utilisation constitue naturellement la limite d’une transaction : un cas d’utilisation = une unité de travail cohérente. Plutôt que de parsemer les services d’appels à beginTransaction(), enveloppez-les dans un décorateur transactionnel afin que le cœur reste indépendant de la persistance.

<?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));
    }
}

Validation&nbsp;: où la placer

Répartissez la validation en deux niveaux :

  • La validation des entrées (format, champs obligatoires) s’effectue dans l’adaptateur entrant ou dans un validateur dédié avant l’exécution du cas d’utilisation.
  • La validation du domaine (invariants, règles métier) réside dans les objets-valeurs et les entités, qui lèvent des exceptions du domaine.

Le cas d’utilisation suppose que les entrées sont correctement formées et en vérifie la signification.

<?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;

Renvoyer un résultat sans HTTP

Deux façons de renvoyer des données tout en restant indépendant de tout framework :

  • Renvoyer un DTO de résultat (simple et synchrone).
  • Port de sortie / présentateur — le cas d’utilisation transmet le résultat à une limite de sortie injectée, laissant l’adaptateur décider du formatage (JSON, HTML, CLI). Ainsi, même la forme de la réponse reste en dehors du cœur.
<?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()));
    }
}

Événements du domaine issus des cas d’utilisation

Les cas d’utilisation enregistrent souvent des événements du domaine déclenchés par les entités, puis les distribuent une fois la transaction validée. Cela découple les effets de bord (envoyer un e-mail de bienvenue, mettre à jour le modèle de lecture) du processus central.

<?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;

Une classe par cas d’utilisation

Préférez une classe à action unique (une seule méthode publique, souvent __invoke) à un service volumineux comportant dix méthodes. Avantages :

  • Responsabilité unique et nommage précis (CancelSubscription, plutôt que SubscriptionService::cancel).
  • Le constructeur n’injecte que ce dont cette opération a besoin.
  • Il est facile de l’envelopper dans des décorateurs (transaction, journalisation, autorisation).

Assemblage à la racine de composition

Le cas d’utilisation ne crée jamais lui-même ses dépendances ; c’est la racine de composition qui s’en charge. Voici un assemblage manuel que vous pourriez placer dans une définition de conteneur 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;

Préoccupations transverses avec des décorateurs

La journalisation, les métriques et l’autorisation sont des préoccupations transverses — gardez-les en dehors du corps du cas d’utilisation. Enveloppez le service dans des décorateurs qui partagent son interface, afin que le cœur reste concentré sur le processus tandis que les préoccupations d’infrastructure se composent autour de lui.

<?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;
    }
}

Vérification rapide

Où doit se trouver la règle « une adresse e-mail doit être unique et correctement formée » ?

Récapitulatif

Les cas d’utilisation indépendants de tout framework vous offrent une couche applicative claire :

  • Une classe à action unique par opération, qui reçoit un DTO de commande et renvoie un DTO de résultat (ou transmet le résultat à un port de sortie).
  • Les entités et les objets-valeurs possèdent les invariants ; le service se contente d’orchestrer — évitez les modèles anémiques.
  • Les cas d’utilisation sont des limites de transaction, enveloppées par des décorateurs plutôt que de contenir directement beginTransaction.
  • Séparez la validation des entrées (adaptateur/objet-valeur) de la validation du domaine (entités).
  • Les événements du domaine découplent les effets de bord ; la racine de composition assemble les dépendances.

Questions Fréquemment Posées

La leçon « Cas d’utilisation et services applicatifs » est-elle gratuite ?

Oui — le texte complet de « Cas d’utilisation et services applicatifs » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours PHP Academy, passe à CoddyKit PRO. Le cours PHP Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Cas d’utilisation et services applicatifs » ?

Exprimez les actions métier sous forme de cas d’utilisation indépendants des frameworks. Tu pratiques PHP Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer PHP Academy ?

Aucune expérience préalable n'est requise. PHP Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 3 sur 4.

Combien de temps prend la leçon « Cas d’utilisation et services applicatifs » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon PHP Academy ?

Oui. Chaque leçon PHP Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. De l’architecture en couches à l’architecture propre
  2. Ports et adaptateurs expliqués
  3. Cas d’utilisation et services applicatifs
  4. Inversion des dépendances en pratique
← Retour à PHP Academy