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 nieSubscriptionService::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
beginTransactionbezpoś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
- Od architektury warstwowej do Clean Architecture
- Wyjaśnienie portów i adapterów
- Przypadki użycia i usługi aplikacyjne
- Odwrócenie zależności w praktyce