0Pricing
PHP Academy · Lekcja

Przypadki użycia i usługi aplikacyjne

Proszę przedstawiać działania biznesowe jako niezależne od frameworków przypadki użycia.

Przypadki użycia i usługi aplikacyjne to bezpłatna lekcja PHP Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej PHP Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs PHP Academy zawiera 4 lekcji w sumie.

Czym naprawdę jest przypadek użycia

Przypadek użycia (zwany także usługą aplikacyjną lub interaktorem) opisuje dokładnie jedną operację właściwą dla aplikacji: Rejestracja użytkownika, Złożenie zamówienia, Anulowanie subskrypcji. Koordynuje encje i porty, aby zrealizować jeden zamiar. Co najważniejsze, jest niezależny od frameworka: nie zawiera Request, Response ani globalnych funkcji pomocniczych — to zwykły PHP, który można wywołać z dowolnego miejsca.

DTO polecenia i wyniku

Przypadek użycia przyjmuje jako dane wejściowe niemutowalny DTO polecenia i zwraca DTO wyniku. DTO to proste nośniki danych — nie zawierają zachowania ani logiki walidacji wykraczającej poza sprawdzenie struktury. Właściwości readonly (PHP 8.1+) zabezpieczają je przed modyfikacją.

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

Ciało usługi aplikacyjnej

Usługa tłumaczy polecenie na operacje domenowe. Odpowiada za orkiestrację na poziomie aplikacji — sprawdzanie unikalności, utrwalanie danych i zwracanie identyfikatorów — a reguły deleguje encjom.

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

Logikę należy umieszczać w encjach

Należy uważać na anemiczny model domeny: encje zredukowane do getterów i setterów, podczas gdy cała logika znajduje się w usługach. Niezmienniki należą do encji. Przypadek użycia powinien przypominać krótki skrypt opisujący intencje, a nie ścianę reguł biznesowych.

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

Granice transakcji

Przypadek użycia jest naturalną granicą transakcji: jeden przypadek użycia oznacza jedną spójną jednostkę pracy. Zamiast zaśmiecać usługi wywołaniami beginTransaction(), należy opakować je dekoratorem transakcyjnym, aby rdzeń pozostał niezależny od sposobu utrwalania danych.

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

Gdzie umieścić walidację

Walidację należy podzielić na dwa poziomy:

  • Walidacja danych wejściowych (format, wymagane pola) odbywa się w adapterze wejściowym lub dedykowanym walidatorze, zanim zostanie uruchomiony przypadek użycia.
  • Walidacja domenowa (niezmienniki, reguły biznesowe) znajduje się w obiektach wartości i encjach, które zgłaszają wyjątki domenowe.

Przypadek użycia zakłada poprawnie uformowane dane i egzekwuje ich znaczenie.

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

Zwracanie wyników bez HTTP

Dwa sposoby zwracania danych przy zachowaniu niezależności od frameworka:

  • Zwrócenie DTO wyniku (proste, synchroniczne).
  • Port wyjściowy / prezenter — przypadek użycia przekazuje wynik do wstrzykniętej granicy wyjściowej, pozwalając adapterowi zdecydować o formacie (JSON, HTML, CLI). Dzięki temu nawet kształt odpowiedzi pozostaje poza rdzeniem.
<?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()));
    }
}

Zdarzenia domenowe z przypadków użycia

Przypadki użycia często gromadzą zdarzenia domenowe zgłaszane przez encje, a następnie rozsyłają je po zatwierdzeniu transakcji. Oddziela to skutki uboczne (wysłanie powitalnej wiadomości e-mail, aktualizacja modelu odczytu) od przepływu pracy rdzenia.

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

Jedna klasa na przypadek użycia

Warto preferować klasę wykonującą jedną operację (jedna metoda publiczna, często __invoke) zamiast rozbudowanej usługi z dziesięcioma metodami. Korzyści:

  • Wyraźna pojedyncza odpowiedzialność i nazewnictwo (CancelSubscription, a nie SubscriptionService::cancel).
  • Konstruktor wstrzykuje tylko zależności potrzebne tej operacji.
  • Łatwe opakowanie dekoratorami (transakcja, logowanie, autoryzacja).

Konfiguracja w composition root

Przypadek użycia nigdy samodzielnie nie tworzy swoich zależności; robi to composition root. Poniżej przedstawiono ręczne skonfigurowanie zależności, które można umieścić w definicji kontenera 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;

Aspekty przekrojowe za pomocą dekoratorów

Logowanie, metryki i autoryzacja to aspekty przekrojowe — nie należy umieszczać ich w ciele przypadku użycia. Należy opakować usługę dekoratorami, które udostępniają ten sam interfejs, aby rdzeń koncentrował się na przepływie pracy, a kwestie infrastrukturalne można było komponować wokół niego.

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

Szybkie sprawdzenie

Gdzie powinna znajdować się reguła „adres e-mail musi być unikalny i mieć poprawny format”?

Podsumowanie

Przypadki użycia niezależne od frameworka zapewniają czystą warstwę aplikacji:

  • Jedna klasa wykonująca jedną operację na operację, przyjmująca DTO polecenia i zwracająca DTO wyniku (lub przekazująca wynik do portu wyjściowego).
  • Encje i obiekty wartości są właścicielami niezmienników; usługa jedynie koordynuje działania — należy unikać anemicznych modeli.
  • Przypadki użycia stanowią granice transakcji, opakowywane dekoratorami zamiast zawierać wywołania beginTransaction bezpośrednio w kodzie.
  • Należy oddzielić walidację danych wejściowych (adapter/obiekt wartości) od walidacji domenowej (encje).
  • Zdarzenia domenowe oddzielają skutki uboczne; composition root konfiguruje zależności.

Często zadawane pytania

Czy lekcja „Przypadki użycia i usługi aplikacyjne” jest bezpłatna?

Tak — pełny tekst „Przypadki użycia i usługi aplikacyjne” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu PHP Academy, przejdź na CoddyKit PRO. Kurs PHP Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Przypadki użycia i usługi aplikacyjne”?

Proszę przedstawiać działania biznesowe jako niezależne od frameworków przypadki użycia. Ćwiczysz PHP Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć PHP Academy?

Nie wymagamy żadnego doświadczenia. PHP Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Przypadki użycia i usługi aplikacyjne”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji PHP Academy?

Tak. Każda lekcja PHP Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Od architektury warstwowej do Clean Architecture
  2. Wyjaśnienie portów i adapterów
  3. Przypadki użycia i usługi aplikacyjne
  4. Odwrócenie zależności w praktyce
← Powrót do PHP Academy