Fáze požadavku: 7. Doménová vrstva & DTO
Modul 1 · Lekce 3

Nativní Enumy, výraz match a jak funguje PSR-4 v composer.json

💡 Shrnutí lekce

Osvojte si string-backed enumy pro bezpečné reprezentace stavů, nahraďte nespolehlivý switch striktním výrazem match a pochopte, jak Composer přes PSR-4 mapuje třídy na soubory.

Nativní Backed Enumy v moderním PHP

Dříve se pevné množiny stavů (např. obtížnost modulu nebo fáze životního cyklu) v PHP řešily pomocí tříd s konstantami:

// Zastaralý přístup:
class OldDifficulty {
    public const BEGINNER = 'beginner';
    public const ADVANCED = 'advanced';
}
function setDifficulty(string $difficulty) {} // Žádná typová kontrola! Můžete předat 'xyz'.

Od PHP 8.1 máme k dispozici nativní Enumy. Pokud enum reprezentuje skalární hodnotu (např. uloženou v databázi), použijeme Backed Enum:

enum ModuleDifficulty: string
{
    case Beginner = 'beginner';
    case Intermediate = 'intermediate';
    case Advanced = 'advanced';
}

Výhody:

  • Typová bezpečnost: Funkce vyžadující ModuleDifficulty odmítne jakýkoliv neplatný řetězec.
  • Vlastní metody: Enum v PHP není jen tupý seznam hodnot – může obsahovat metody, rozhraní a bohaté chování.
  • Přístup k hodnotě: Hodnotu získáte přes $enum->value, název konstanty přes $enum->name.

Výraz match: Moderní, striktní náhrada za switch

Tradiční konstrukce switch v PHP trpí několika závažnými problémy:

  • Používá slabé porovnávání (==), což způsobuje bizarní chyby při porovnávání řetězců a čísel.
  • Vyžaduje klíčové slovo break, jinak propadává do dalších větví (fall-through).
  • Nevrací hodnotu přímo jako výraz.

V PHP 8.0 přišel výraz match:

public function label(): string
{
    return match ($this) {
        self::Beginner => 'Začátečník',
        self::Intermediate => 'Pokročilý',
        self::Advanced => 'Architekt / Produkce',
    };
}

Klíčové vlastnosti výrazu match:

  1. Striktní porovnání: Vždy používá operátor identity ===.
  2. Návratová hodnota: Je to plnohodnotný výraz (expression), jehož výsledek můžete přímo vrátit (return match) nebo přiřadit do proměnné.
  3. Exhaustiveness check: Pokud některý stav enumu nepokryjete a neuvedete větev default, PHP vyhodí výjimku UnhandledMatchError. To je obrovská výhoda – pokud do enumu přidáte novou hodnotu, PHP engine i statická analýza (Larastan) vás okamžitě upozorní na všechna místa, kde chybí její obsluha.

Jak funguje PSR-4 Autoloading v composer.json

Aby PHP engine věděl, kde na disku najít třídu App\Enums\ModuleDifficulty, využívá standard PSR-4 definovaný v souboru composer.json:

"autoload": {
    "psr-4": {
        "App\\": "app/",
        "Database\\Factories\\": "database/factories/",
        "Database\\Seeders\\": "database/seeders/"
    }
}

Pravidlo funguje následovně:

  1. Prefix jmenného prostoru App\ odpovídá kořenové složce app/.
  2. Každé další zanoření v namespace odpovídá podadresáři se stejným názvem.
  3. Třída App\Enums\ModuleDifficulty se proto musí nacházet v souboru app/Enums/ModuleDifficulty.php.

Při instalaci nebo po spuštění příkazu composer dump-autoload vygeneruje Composer optimalizovanou mapu do složky vendor/composer/autoload_psr4.php. Když kód poprvé použije třídu, registrovaný Composer autoloader bleskově vyhledá a načte příslušný soubor.

💻 Klíčová konstrukce z praxe
<?php

declare(strict_types=1);

namespace App\Enums;

// Ukázka string-backed enumu s výrazem match (app/Enums/LifecyclePhase.php)
enum LifecyclePhase: string
{
    case Entry = 'entry';
    case Bootstrap = 'bootstrap';
    case Container = 'container';
    case Routing = 'routing';
    case Middleware = 'middleware';
    case Controller = 'controller';
    case Domain = 'domain';
    case Database = 'database';
    case View = 'view';
    case Testing = 'testing';
    case Deployment = 'deployment';

    // Elegantní mapování pořadí kroků pomocí striktního výrazu match
    public function stepOrder(): int
    {
        return match ($this) {
            self::Entry => 1,
            self::Bootstrap => 2,
            self::Container => 3,
            self::Routing => 4,
            self::Middleware => 5,
            self::Controller => 6,
            self::Domain => 7,
            self::Database => 8,
            self::View => 9,
            self::Testing => 10,
            self::Deployment => 11,
        };
    }
}

🎯 Ověření znalostí z lekce

Kvíz otestuje pochopení architektury a reálného kódu probíraného v této lekci.

Otázka 1 z 3

Jaký je zásadní rozdíl v porovnávání hodnot mezi výrazem match a konstrukcí switch v PHP?

Otázka 2 z 3

Jak v PHP získáte podkladovou skalární hodnotu z backed enumu ModuleDifficulty::Beginner?

Otázka 3 z 3

Co přesně vyjadřuje konfigurace "App\": "app/" v sekci autoload.psr-4 souboru composer.json?

Zodpovězeno: 0 / 3
🔬 Živá anatomie v této aplikaci
3 soubory
Na co se v kódu zaměřit: Podívej se do app/Enums/LifecyclePhase.php na metody stepOrder() a label(), kde výraz match zajišťuje vyčerpávající mapování všech 11 fází životního cyklu požadavku. V composer.json si v sekci "autoload" prohlédni PSR-4 mapování jmenných prostorů App\ a Database\.
cesta: app/Enums/ModuleDifficulty.php
30 řádků · 0.8 KB
Enum určující obtížnost modulu (Začátečník, Pokročilý, Architekt) s Tailwind CSS badge třídami.
1 <?php
2
3 declare(strict_types=1);
4
5 namespace App\Enums;
6
7 enum ModuleDifficulty: string
8 {
9 case Beginner = 'beginner';
10 case Intermediate = 'intermediate';
11 case Advanced = 'advanced';
12
13 public function label(): string
14 {
15 return match ($this) {
16 self::Beginner => 'Začátečník',
17 self::Intermediate => 'Pokročilý',
18 self::Advanced => 'Architekt / Produkce',
19 };
20 }
21
22 public function badgeClass(): string
23 {
24 return match ($this) {
25 self::Beginner => 'bg-emerald-500/10 text-emerald-400 ring-1 ring-inset ring-emerald-500/20',
26 self::Intermediate => 'bg-amber-500/10 text-amber-400 ring-1 ring-inset ring-amber-500/20',
27 self::Advanced => 'bg-rose-500/10 text-rose-400 ring-1 ring-inset ring-rose-500/20',
28 };
29 }
30 }
31
cesta: app/Enums/LifecyclePhase.php
71 řádků · 3.4 KB
PHP 8.4 string enum definující 11 kroků životního cyklu požadavku v Laravelu 12 s pořadím a popisky.
1 <?php
2
3 declare(strict_types=1);
4
5 namespace App\Enums;
6
7 enum LifecyclePhase: string
8 {
9 case Entry = 'entry';
10 case Bootstrap = 'bootstrap';
11 case Container = 'container';
12 case Routing = 'routing';
13 case Middleware = 'middleware';
14 case Controller = 'controller';
15 case Domain = 'domain';
16 case Database = 'database';
17 case View = 'view';
18 case Testing = 'testing';
19 case Deployment = 'deployment';
20
21 public function stepOrder(): int
22 {
23 return match ($this) {
24 self::Entry => 1,
25 self::Bootstrap => 2,
26 self::Container => 3,
27 self::Routing => 4,
28 self::Middleware => 5,
29 self::Controller => 6,
30 self::Domain => 7,
31 self::Database => 8,
32 self::View => 9,
33 self::Testing => 10,
34 self::Deployment => 11,
35 };
36 }
37
38 public function label(): string
39 {
40 return match ($this) {
41 self::Entry => '1. Vstupní bod (public/index.php)',
42 self::Bootstrap => '2. Bootstrap aplikace (bootstrap/app.php)',
43 self::Container => '3. Service Container & Providers',
44 self::Routing => '4. Routing & Matchování tras',
45 self::Middleware => '5. Middleware Pipeline',
46 self::Controller => '6. Controller & Action',
47 self::Domain => '7. Doménová vrstva & DTO',
48 self::Database => '8. Databáze & Eloquent ORM',
49 self::View => '9. Blade & Livewire prezentace',
50 self::Testing => '10. Pest testování & QA',
51 self::Deployment => '11. FrankenPHP & Produkční běh',
52 };
53 }
54
55 public function description(): string
56 {
57 return match ($this) {
58 self::Entry => 'Požadavek přichází do webserveru (FrankenPHP) a předává se do public/index.php, kde se načítá Composer autoloader a startuje runtime.',
59 self::Bootstrap => 'Vytvoření instance Application v bootstrap/app.php a konfigurace rout, middlewarů a výjimek v Laravelu 12.',
60 self::Container => 'Registrace a bootování Service Providerů, navázání rozhraní na implementace a konfigurace služeb v IoC kontejneru.',
61 self::Routing => 'Vyhodnocení příchozího HTTP požadavku podle tras definovaných v routes/web.php a matchování na příslušnou akci či komponentu.',
62 self::Middleware => 'Průchod řetězcem globálních a routových middleware vrstev pro CORS, session, šifrování cookies a ověření požadavku.',
63 self::Controller => 'Předání řízení do Controlleru nebo Livewire komponenty, validace vstupních dat a spuštění doménových služeb.',
64 self::Domain => 'Běh čisté byznys logiky, transformace do neměnných DTO struktur a správa stavu nezávisle na frameworku.',
65 self::Database => 'Práce s PostgreSQL databází přes Eloquent ORM, migrace, relační modely, transakce a prevence N+1 dotazů.',
66 self::View => 'Sestavení HTML šablony přes Blade a reaktivních komponent Livewire 3 pro interaktivní zobrazení v prohlížeči.',
67 self::Testing => 'Automatizované ověření spolehlivosti aplikace sadou Pest PHP testů, architektonických testů a statické analýzy.',
68 self::Deployment => 'Kontejnerizace aplikace v multi-stage Dockerfile s FrankenPHP worker módem a spuštění za Caddy reverzní proxy na Hetzner serveru.',
69 };
70 }
71 }
72
cesta: composer.json
87 řádků · 2.8 KB
Projektový manifest Composeru definující PHP 8.4, Laravel 12 závislosti a PSR-4 autoloading.
1 {
2 "$schema": "https://getcomposer.org/schema.json",
3 "name": "laravel/laravel",
4 "type": "project",
5 "description": "The skeleton application for the Laravel framework.",
6 "keywords": ["laravel", "framework"],
7 "license": "MIT",
8 "require": {
9 "php": "^8.2",
10 "laravel/framework": "^12.0",
11 "laravel/tinker": "^2.10.1",
12 "livewire/livewire": "^3.0"
13 },
14 "require-dev": {
15 "fakerphp/faker": "^1.23",
16 "laravel/pail": "^1.2.2",
17 "laravel/pint": "^1.24",
18 "laravel/sail": "^1.41",
19 "mockery/mockery": "^1.6",
20 "nunomaduro/collision": "^8.6",
21 "phpunit/phpunit": "^11.5.50"
22 },
23 "autoload": {
24 "psr-4": {
25 "App\\": "app/",
26 "Database\\Factories\\": "database/factories/",
27 "Database\\Seeders\\": "database/seeders/"
28 }
29 },
30 "autoload-dev": {
31 "psr-4": {
32 "Tests\\": "tests/"
33 }
34 },
35 "scripts": {
36 "setup": [
37 "composer install",
38 "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"",
39 "@php artisan key:generate",
40 "@php artisan migrate --force",
41 "npm install",
42 "npm run build"
43 ],
44 "dev": [
45 "Composer\\Config::disableProcessTimeout",
46 "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1 --timeout=0\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite --kill-others"
47 ],
48 "test": [
49 "@php artisan config:clear --ansi",
50 "@php artisan test"
51 ],
52 "post-autoload-dump": [
53 "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
54 "@php artisan package:discover --ansi"
55 ],
56 "post-update-cmd": [
57 "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
58 ],
59 "post-root-package-install": [
60 "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
61 ],
62 "post-create-project-cmd": [
63 "@php artisan key:generate --ansi",
64 "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
65 "@php artisan migrate --graceful --ansi"
66 ],
67 "pre-package-uninstall": [
68 "Illuminate\\Foundation\\ComposerScripts::prePackageUninstall"
69 ]
70 },
71 "extra": {
72 "laravel": {
73 "dont-discover": []
74 }
75 },
76 "config": {
77 "optimize-autoloader": true,
78 "preferred-install": "dist",
79 "sort-packages": true,
80 "allow-plugins": {
81 "pestphp/pest-plugin": true,
82 "php-http/discovery": true
83 }
84 },
85 "minimum-stability": "stable",
86 "prefer-stable": true
87 }
88
Zdroj: skutečné soubory této aplikace Plný průzkumník anatomie →